Interview Prep Zoneby CuriouserLabs

Question 2 of 5 · senior level

Explain PECS (producer extends, consumer super). When do you use wildcards vs type parameters?

🎤 Say this first

Producer `extends`: if you only read T's from a structure, take Collection<? extends T> — callers can pass collections of subtypes. Consumer `super`: if you only write T's into it, take Collection<? super T>. Collections.copy(List<? super T> dst, List<? extends T> src) is the canonical signature. Use wildcards for flexibility in method parameters; use a named type parameter when the same type must appear more than once or be returned.

The full picture

PECS in action
// Producer: we only READ from src  -> ? extends
// Consumer: we only WRITE to dst    -> ? super
static <T> void copy(List<? super T> dst, List<? extends T> src) {
    for (T t : src) dst.add(t);
}

List<Integer> ints = List.of(1, 2, 3);
List<Number> nums = new ArrayList<>();
copy(nums, ints);            // works: Integer produced, Number consumes

// Why the rules exist:
List<? extends Number> producer = ints;
producer.add(1);             // COMPILE ERROR — might be a List<Double>!
Number n = producer.get(0);  // fine — whatever it is, it's a Number

List<? super Integer> consumer = nums;
consumer.add(42);            // fine — Integer fits any supertype list
Object o = consumer.get(0);  // only Object comes back out
  • Return types should almost never be wildcards — they force every caller to deal with ?.
  • If a type variable appears once in a signature, prefer a wildcard; if it links two parameters or parameter-and-return, use <T>.
  • Comparator<? super T> in sort() is consumer-super in the wild: a Comparator<Object> can sort anything.

🔄 Likely follow-up questions

  • Why can't you add anything (except null) to a List<? extends Number>?
  • When is an unbounded wildcard List<?> the right declaration?
  • How does Kotlin's declaration-site variance (in/out) map onto PECS?