skip to content

Explain the PECS rule and why a method that copies elements into a destination should declare it as `List<? super T>`.

level: seniorimportance: should knowfreq 62%

answer

  1. Producer Extends, Consumer Super
  2. read → extends; write → super
  3. both read+write → exact T
  4. copy: dest super, src extends
  5. never wildcard a return type

basics

~20 s

PECS means "Producer Extends, Consumer Super." Something you read from uses extends; something you write to uses super. A copy destination is written to (a consumer), so it should be List<? super T> to accept T and its subtypes.

solid answer

~40 s

PECS — Producer Extends, Consumer Super — is Joshua Bloch's mnemonic for choosing wildcards on API parameters. If a parameter *produces* values you read out, use `<? extends T>` so any subtype source is accepted. If it *consumes* values you write in, use `<? super T>` so any supertype destination is accepted. A copy method does both: it reads from a source (producer → `<? extends T>`) and writes into a destination (consumer → `<? super T>`). Declaring the destination as `List<? super T>` makes the API maximally flexible — you can copy a `List<Integer>` into a `List<Number>` or a `List<Object>` — while staying type-safe, because writing a T (or subtype) into a slot that holds a supertype of T is always valid. The JDK's `Collections.copy`, `addAll`, and `fill` follow exactly this pattern.

go deeper

for a junior

Can recite PECS as a phrase and pick super for a write parameter when prompted.

for a middle

Applies PECS to a copy/addAll method correctly and explains read-vs-write reasoning.

for a senior

Designs flexible generic APIs with PECS, cites JDK examples, and knows not to wildcard return types.

for a principal

Weighs API ergonomics vs. complexity, decides where wildcards are worth it, and relates PECS to declaration-site variance in other languages.

## The problem PECS solves Java generics are **invariant**: `List<Integer>` is not a subtype of `List<Number>`. This keeps the type system sound but makes generic *methods* rigid — a method taking `List<Number>` would reject a `List<Integer>` argument. **Wildcards** restore flexibility at the call site while preserving safety. The hard part is choosing **which** wildcard. That is what **PECS** answers. ## PECS spelled out **Producer Extends, Consumer Super.** - A parameter is a **producer** of T if your method *reads* T values out of it. Use `<? extends T>`. You can read T (covariance), but you can't safely write (you don't know the exact subtype). - A parameter is a **consumer** of T if your method *writes* T values into it. Use `<? super T>`. You can write T and its subtypes (contravariance), but reads come back only as Object. If a parameter is both read and written as T, use an exact type `T` (no wildcard). ## Worked example: a copy method ```java public static <T> void copy(List<? super T> dest, List<? extends T> src) { for (int i = 0; i < src.size(); i++) { T element = src.get(i); // src PRODUCES T → extends dest.set(i, element); // dest CONSUMES T → super } } ``` - `src` is read from → producer → `List<? extends T>`. This lets `src` be a `List<Integer>` when T is Number. - `dest` is written to → consumer → `List<? super T>`. This lets `dest` be a `List<Number>` or `List<Object>` when T is Integer. Without these wildcards you could only copy `List<T>` into `List<T>` — far less useful. With them: ```java List<Integer> ints = List.of(1, 2, 3); List<Number> nums = new ArrayList<>(List.of(0, 0, 0)); copy(nums, ints); // T inferred as Integer (or Number); both wildcards satisfied ``` ## Why `<? super T>` is the *right* choice for a destination A destination only ever receives writes of T (or subtypes of T). Any list whose element type is a *supertype* of T can hold a T safely, because a T **is-a** that supertype. So `<? super T>` is exactly the set of valid destinations — no more, no less. Narrowing it to `List<T>` would needlessly reject valid `List<Number>`/`List<Object>` destinations. ## Real JDK signatures that follow PECS - `Collections.copy(List<? super T> dest, List<? extends T> src)` - `Collections.fill(List<? super T> list, T obj)` - `Collections.addAll(Collection<? super T> c, T... elements)` - `Stream.forEach(Consumer<? super T> action)` — a `Consumer<? super T>` consumes T. - `Comparator` factory methods take `Comparator<? super T>`. ## Caveat / good taste Don't add a wildcard to a *return type* — it forces wildcards onto every caller. And don't over-wildcard internal or single-use methods; PECS pays off mostly on **public, reusable** APIs where caller flexibility matters.

  • In `Collections.copy(List<? super T> dest, List<? extends T> src)`, which argument is the producer and which is the consumer?
    `src` is the producer (you read T out of it → extends); `dest` is the consumer (you write T into it → super).
  • Why shouldn't you use a wildcard on a method's return type?
    A wildcard in the return type leaks into the caller's code: the caller is forced to deal with a wildcard type (e.g. can only read as Object), making the API harder to use. Return concrete types instead.

saying these in an interview costs you the question

  • Swapping the rule — using extends for a consumer or super for a producer.
  • Putting a wildcard on the return type (forces wildcards on all callers).
  • Claiming `<? super T>` lets you read T back (only Object).
  • Adding wildcards everywhere, including private/internal methods, hurting readability.

context