skip to content

Apply PECS: in a method that copies elements from one list to another, which parameter gets `<? extends T>` and why?

level: seniorimportance: should knowfreq 55%

answer

  1. PECS = Producer Extends, Consumer Super
  2. source = producer = extends
  3. dest = consumer = super
  4. both read+write → use plain List<T>
  5. Collections.copy(dest super, src extends)

basics

~10 s

The source list (the one you read from) gets <? extends T> because it produces elements. The destination list (the one you write to) gets <? super T> because it consumes them.

solid answer

~40 s

PECS means Producer Extends, Consumer Super. In a `copy(dest, src)` method, `src` is the producer — you only read elements out of it — so it should be `List<? extends T>`, which lets callers pass a `List<T>` or any `List` of a subtype. `dest` is the consumer — you only write elements into it — so it should be `List<? super T>`, accepting a `List<T>` or any `List` of a supertype. This is exactly how `Collections.copy(List<? super T> dest, List<? extends T> src)` is declared. Using `extends` on the source widens the set of acceptable source lists while keeping reads type-safe (each element is guaranteed a `T`). If you used a bare `List<T>` for the source instead, you'd needlessly reject `List<SubtypeOfT>` callers. The mnemonic captures the asymmetry: covariant for producers, contravariant for consumers.

code

java · 13 lines
java
// Producer Extends, Consumer Super (the real Collections.copy signature)
public static <T> void copy(List<? super T> dest,   // consumer: we write into it
                            List<? extends T> src) { // producer: we read from it
    for (int i = 0; i < src.size(); i++) {
        T value = src.get(i);   // read guaranteed to be a T (upper bound)
        dest.set(i, value);     // write a T into a list of some supertype of T
    }
}

// Usage that only compiles thanks to the wildcards:
List<Object> dest = new ArrayList<>(List.of(0, 0, 0));
List<Integer> src = List.of(1, 2, 3);
copy(dest, src); // T = Integer; src is List<? extends Integer>, dest is List<? super Integer>

go deeper

for a junior

Can recite PECS and identify that the list you read from gets extends.

for a middle

Correctly assigns extends to the source and super to the destination in a copy method and explains why each widens callers.

for a senior

Reproduces the Collections.copy signature from first principles, knows the 'both producer and consumer → use plain T' rule, and cites other JDK uses.

for a principal

Designs public APIs with wildcards that maximize caller flexibility, weighs wildcard vs bounded type variable for usability, and reasons about variance interactions across an API surface.

## The problem PECS solves When you write a generic method that moves data, you want the **widest** set of callers to be able to use it without sacrificing type safety. Wildcards are the tool; PECS tells you which wildcard to put where. **PECS = Producer Extends, Consumer Super** (coined by Joshua Bloch in *Effective Java*). - A **producer** is a parameter you only **read from** (it produces values you consume). Give it `<? extends T>`. - A **consumer** is a parameter you only **write to** (it consumes values you provide). Give it `<? super T>`. ## 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++) { dest.set(i, src.get(i)); } } ``` Reasoning element by element: - `src.get(i)` **produces** a value. We only read from `src`, so `src` is the producer → `List<? extends T>`. Each read is guaranteed to be a `T` (the upper bound), which is exactly the type we hand to `dest`. - `dest.set(i, value)` **consumes** the value. We only write to `dest`, so `dest` is the consumer → `List<? super T>`. A `List` of any supertype of `T` can safely hold a `T`. This is the real signature of `java.util.Collections.copy`. ## Why `extends` on the source widens callers If `src` were `List<T>`, then `copy(dst, new ArrayList<Integer>())` with `T = Number` would **fail** — `List<Integer>` is not a `List<Number>` (invariance). With `List<? extends Number>`, it compiles, because `List<Integer>` IS a `List<? extends Number>` (covariance). So `extends` on the producer maximizes the source lists you can pass. ## Why `super` on the destination widens callers Symmetrically, `dest` as `List<? super Number>` lets you copy `Number`s into a `List<Number>` **or** a `List<Object>` — any list that can hold a `Number`. That's contravariance: it varies opposite to the element subtyping. ## The "both" case: don't use a wildcard If a parameter is **both** produced from and consumed into within the same method, neither wildcard works — you can't read-as-`T` AND write-as-`T` through a single wildcard. Use a plain type parameter `List<T>` there. The rule of thumb: "if a type parameter appears only once in the method and you only read or only write, a wildcard is right; if you do both, use the type variable." ## Other library examples - `Collections.max(Collection<? extends T> coll)` — reads (produces) elements to compare. - `Stream.forEach(Consumer<? super T> action)` — the consumer accepts a `T`, so `super`. - `Comparator` / `Comparable` signatures lean on `super` heavily because they consume the compared object. ## Common mistakes - Putting `extends` on the destination — then you can't write to it (it's read-only). - Putting `super` on the source — then reads only give you `Object`, losing the `T` guarantee. - Using wildcards on a parameter that is both produced and consumed. PECS turns the abstract covariance/contravariance rules into a one-line decision procedure you can apply at the keyboard.

  • When should a parameter use a plain type variable `T` instead of a wildcard?
    When the method both reads from and writes to that parameter (it is both producer and consumer), a single wildcard can't serve both, so you use `List<T>`.
  • What is the wildcard in `Comparator<? super T>` (e.g. in `Collections.sort`) doing?
    It's the consumer side: a comparator consumes `T` values to compare, so allowing any comparator of a supertype of `T` widens the acceptable comparators.

saying these in an interview costs you the question

  • Putting `extends` on the destination (makes it un-writable).
  • Putting `super` on the source (reads degrade to Object).
  • Using a wildcard on a parameter that is both read from and written to.
  • Thinking PECS is about performance rather than type safety and caller flexibility.

context