Interview Prep Zoneby CuriouserLabs

Question 4 of 5 · architect level

Design question: how do you use bounded and recursive type parameters to build safe, fluent APIs?

🎤 Say this first

Bounded type parameters (<T extends Comparable<? super T>>) let APIs demand capabilities without concrete types. Recursive bounds (Enum<E extends Enum<E>>, Builder<SELF extends Builder<SELF>>) let a supertype talk about the implementing subtype — the trick behind fluent builder hierarchies that return the right type at each step. Use them where they remove casts for callers; stop before the signature needs a diagram — API readability is part of correctness.

The full picture

The self-type (curiously recurring) builder
abstract class Request<SELF extends Request<SELF>> {
    String url;
    @SuppressWarnings("unchecked")
    SELF url(String u) { this.url = u; return (SELF) this; }
}

final class JsonRequest extends Request<JsonRequest> {
    JsonRequest body(Object o) { …; return this; }
}

// Fluent chain keeps the SUBtype — no casts, order-independent:
new JsonRequest().url("/orders").body(dto);   // url() returned JsonRequest ✅
  • <T extends Comparable<? super T>> — the canonical 'max' bound: allows T to inherit its comparability from a supertype (e.g., java.sql.Timestamp comparing as java.util.Date).
  • Intersection bounds: <T extends Runnable & AutoCloseable> demand multiple capabilities without a new interface.
  • Type tokens (Class<T>) + bounded factories give type-safe heterogeneous containers — <T> T getInstance(Class<T> type) in DI containers is exactly this.

🔄 Likely follow-up questions

  • Why is Enum declared as Enum<E extends Enum<E>>?
  • How does the JDK's Collectors.toMap signature apply PECS?
  • When do you give up on generics and accept a runtime check?