Course contents

Wildcards and PECS

Wildcards, invariance, PECS, wildcard capture, collection API signatures and when not to expose wildcard return types.

PremiumMust know for interviewsLesson16 min readCoreReviewed Oct 2026 · Java 21
On this page
  1. Picture it first: three lanes through the same hierarchy
  2. Wildcards under the hood: invariance first
  3. ? extends T: producer, readable, not writable
  4. ? super T: consumer, writable, read as Object
  5. PECS in real APIs: copy, addAll and stack
  6. Advanced: comparators, max and captured wildcards
  7. When not to use wildcards
  8. How to say it in the interview

🧭 What are wildcards in Java generics?

A wildcard is a type argument written as ? when the caller should not have to name the exact element type. List<?> means "a list of some type I don't know", List<? extends Number> means "some subtype of Number", and List<? super Integer> means "some supertype of Integer". It acts like a one-way adapter on a box: sometimes you can safely take things out, sometimes you can safely put things in, but rarely both.

For this lesson, we'll focus on the parts that show up in code reviews and interviews: invariance, unbounded ?, upper-bounded ? extends T, lower-bounded ? super T, PECS, Collections.copy, stack APIs, Comparator<? super T>, wildcard capture, return-type mistakes, and how Java's use-site variance compares with Kotlin and C#. We'll lean on the basics from Generics basics and the runtime story from How type erasure works rather than repeating them.

Below the line (out of scope)

The 30-second answer

Java generics are invariant: List<Integer> is not a List<Number>, because otherwise someone could add a Double to a list of integers. Use List<?> when you only need operations that work for any element type, mostly reading as Object and adding only null. Use ? extends T when the structure produces T values for you, so reads are safe but writes are not. Use ? super T when the structure consumes T values from you, so writes are safe but reads come back only as Object. That's PECS: Producer Extends, Consumer Super, from Effective Java Item 31. Use wildcards on input parameters to make APIs flexible, but don't return wildcard types unless you enjoy making every caller fight the compiler.

What you’ll learn

  • Explain why List<Integer> is not a List<Number> even though Integer extends Number
  • Use unbounded ? when you only need Object reads and size or iteration operations
  • Choose ? extends T for producers and explain why adding non-null values fails
  • Choose ? super T for consumers and explain why reads come back as Object
  • Apply PECS to Collections.copy, addAll, stack APIs and Comparator<? super T>
  • Use a private helper method to handle wildcard capture safely
  • Avoid wildcard return types and compare Java use-site variance with Kotlin out/in and C# out/in

Interview radar

Asked very often: Why does List<? extends Number> let me read Number but not add Integer? Core follow-up: Explain Collections.copy(List<? super T> dest, List<? extends T> src). Production angle: A reusable utility is too strict (List<Number> instead of List<? extends Number>), so every caller starts copying collections just to satisfy the signature.

01Picture it first: three lanes through the same hierarchy

Let's get the picture right first. Imagine Object, Number and Integer as a type hierarchy, then put a list gate in front of it. An extends gate lets values flow out toward a wider type, while a super gate lets specific values flow in toward a wider container.

Fig. 1

Object, Number and Integer hierarchy with extends arrows showing safe reads upward and super arrows showing safe writes downward

Object, Number and Integer hierarchy with extends arrows showing safe reads upward and super arrows showing safe writes downward

Notice the direction. ? extends Number accepts a List<Integer>, but the compiler refuses add(10) because the real list might be List<Double>. ? super Integer accepts a List<Number> or List<Object>, so adding an Integer is safe, but reading gives only Object because the actual stored values might be any supertype's mixture.

02Wildcards under the hood: invariance first

Java arrays are covariant: a String[] can be treated as an Object[], and a bad store fails at runtime with ArrayStoreException. Generics took the other road. A List<Integer> is not a subtype of List<Number>, so the bad store fails at compile time.

Try it yourself first

Before reading on, predict what would go wrong if Java allowed List<Integer> to be assigned to List<Number>.

import java.util.ArrayList;
import java.util.List;

List<Integer> integers = new ArrayList<>();
integers.add(10);

// If this were allowed, the next line would corrupt integers.
// List<Number> numbers = integers;
// numbers.add(3.14);

Integer first = integers.getFirst();

Notice three things:

  • Integer extends Number, but List<Integer> does not extend List<Number>.
  • Invariance protects writes. Without it, a list promised to contain integers could receive a Double.
  • Wildcards are Java's way to recover flexibility without giving up type safety.

The unbounded wildcard, List<?>, is the most honest signature when the element type genuinely does not matter. You can read elements, but the best static type is Object. You can add only null, because null belongs to every reference type.

import java.util.List;

static int countNonNull(List<?> values) {
    int count = 0;
    for (Object value : values) {
        if (value != null) count++;
    }
    return count;
}

static void addMarker(List<?> values) {
    values.add(null);          // legal, but rarely useful
    // values.add("x");       // compile error: element type is unknown
}

I use List<?> for operations like size, isEmpty, logging, metrics and membership checks where I don't care whether the list holds Order, String or UUID. The simple option is List<Object>, but that rejects List<String>, which is almost never what you wanted.

03? extends T: producer, readable, not writable

? extends Number means "some specific type that is Number or below". The compiler knows every element can be viewed as a Number, so reads are safe. It does not know which exact subtype the list owns, so writing a non-null value is unsafe.

Premium lesson

Keep reading with the Java Pass

The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.

  • ? super T: consumer, writable, read as Object
  • PECS in real APIs: copy, addAll and stack
  • Advanced: comparators, max and captured wildcards
  • When not to use wildcards
  • How to say it in the interview

From ₹199 · one-time payment · No auto-renewal

Practise this topicGenerics: interview questions & scenarios
Further reading (6)