Question 23 · Section 20

What is recursive type bound

Structured Java interview answer with junior, middle, and senior-level explanation.

Language versions: English Russian Ukrainian

🟢 Junior Level

Recursive type bound — when a type parameter is bounded by itself or by a type that also uses this parameter.

// T must be Comparable to itself
public class SortedList<T extends Comparable<T>> {
    private List<T> list = new ArrayList<>();

    public void add(T item) {
        list.add(item);
        list.sort(Comparable::compareTo);
    }
}

SortedList<Integer> ints = new SortedList<>();    // ✅ Integer implements Comparable<Integer>
SortedList<String> strings = new SortedList<>();  // ✅ String implements Comparable<String>

Simple analogy: “I can compare myself with others like me”


🟡 Middle Level

How it works

Classic example — Enum:

// Java Enum declaration
public abstract class Enum<E extends Enum<E>>
    implements Comparable<E> {

    public final int compareTo(E other) {
        return this.ordinal - other.ordinal;
    }
}

// Usage
enum Color { RED, GREEN, BLUE }
// Color extends Enum<Color>
// Color implements Comparable<Color>

Why it’s needed:

// Without recursive bound — cannot guarantee type safety
public class Box<T extends Comparable<T>> {
    private T value;

    public int compare(T other) {
        return value.compareTo(other);  // ✅ type-safe
    }
}

Box<Integer> box = new Box<>();
box.compare(42);  // OK

// Box<Object> box2 = new Box<>();  // ❌ Object does not implement Comparable<Object>

Self-bounding types

Pattern for fluent interfaces:

public abstract class Builder<T, B extends Builder<T, B>> {
    protected abstract B self();

    public B withName(String name) {
        // ... setup
        return self();
    }

    public B withAge(int age) {
        // ... setup
        return self();
    }

    public T build() {
        // ... build
    }
}

public class UserBuilder extends Builder<User, UserBuilder> {
    @Override
    protected UserBuilder self() { return this; }
}

// Fluent API
User user = new UserBuilder()
    .withName("John")
    .withAge(25)
    .build();

Common mistakes

  1. Wrong recursive bound: ```java // ❌ T extends Comparable — not recursive public class BadBox<T extends Comparable> { }

// ✅ T extends Comparable — recursive public class GoodBox<T extends Comparable> { }


2. **Too strict bound:**
```java
// ❌ T must be exactly Comparable<T>
public class Box<T extends Comparable<T>> { }

// ⚠️ Won't pass for some types
// Some classes implement Comparable to a supertype

🔴 Senior Level

Internal Implementation

Type erasure with recursive bounds:

public class Box<T extends Comparable<T>> {
    private T value;
}

// After erasure: T -> Comparable (first bound)
public class Box {
    private Comparable value;
}

Nested recursive bounds:

// Complex case — Enum<E extends Enum<E>>
// T must be Enum<T>
// Enum<T> implements Comparable<T>
// Therefore T implements Comparable<T>

public class Enum<E extends Enum<E>> implements Comparable<E> {
    // E — the specific enum type
    // compareTo accepts the same enum type
}

Architectural Trade-offs

Recursive bounds vs regular:

Recursive Regular
Type-safe comparison May be less precise
Fluent interfaces Simpler
Harder to understand Easier

Edge Cases

1. Multiple recursive bounds:

public class Graph<N extends Node<N, E>, E extends Edge<N, E>> {
    private List<N> nodes;
    private List<E> edges;
}

public interface Node<N extends Node<N, E>, E extends Edge<N, E>> {
    List<E> getEdges();
}

public interface Edge<N extends Node<N, E>, E extends Edge<N, E>> {
    N getSource();
    N getTarget();
}

2. Relaxed recursive bound:

// T must be Comparable to a supertype of T
public class SortedList<T extends Comparable<? super T>> {
    // This relaxes the bound — allows subclasses to work too
}

// ? super T is needed because subclasses inherit Comparable<SuperType>.
// For example, Dog extends Animal implements Comparable<Animal>.
// Without ? super T, Dog would not satisfy Comparable<Dog>.

// For example:
class Date implements Comparable<Date> { }
class java.sql.Date extends Date { }
// java.sql.Date implements Comparable<Date> (via inheritance)
// But not Comparable<java.sql.Date>
// With ? super T — it works!

3. CRTP (Curiously Recurring Template Pattern):

// Real usage: Enum<E extends Enum<E>> — each enum type
// compares only with itself. This guarantees type-safe compareTo.

// Pattern from C++, adapted for Java
public abstract class SelfComparable<T extends SelfComparable<T>>
    implements Comparable<T> {

    public abstract int compareTo(T other);
}

public class MyType extends SelfComparable<MyType> {
    private int value;

    @Override
    public int compareTo(MyType other) {
        return Integer.compare(this.value, other.value);
    }
}

Performance

Recursive bounds:
- Runtime: Zero overhead
- Compile time: complex checking
- Type erasure: bound becomes the type

Performance is the same for all bound types

Production Experience

JDK examples:

// Enum — classic recursive bound
public abstract class Enum<E extends Enum<E>>
    implements Comparable<E> { }

// Collections.max
public static <T extends Object & Comparable<? super T>> T max(
    Collection<? extends T> coll
) { }

// Builder pattern
public abstract class AbstractQueryBuilder<B extends AbstractQueryBuilder<B>> {
    protected abstract B self();

    public B where(String condition) {
        // ...
        return self();
    }
}

Real-world example:

// Entity with self-type for builder
public abstract class BaseEntity<E extends BaseEntity<E>> {
    private Long id;

    @SuppressWarnings("unchecked")
    public E withId(Long id) {
        this.id = id;
        return (E) this;
    }
}

public class User extends BaseEntity<User> {
    private String name;

    public User withName(String name) {
        this.name = name;
        return this;
    }
}

User user = new User()
    .withId(1L)
    .withName("John");

Best Practices

// ✅ Recursive bound for comparison
public class SortedList<T extends Comparable<T>> { }

// ✅ Relaxed bound for subclasses
public class SortedList<T extends Comparable<? super T>> { }

// ✅ Self-bounding for fluent API
public abstract class Builder<B extends Builder<B>> {
    protected abstract B self();
}

// ❌ Overly complex recursive bounds
// ❌ Recursive bounds without need

🎯 Interview Cheat Sheet

Must know:

  • Recursive type bound: <T extends Comparable<T>> — type bounded by itself
  • Classic example — Enum<E extends Enum<E>> — each enum compares with itself
  • Self-bounding types for fluent API: Builder<B extends Builder<B>> returns B
  • Relaxed recursive bound: <T extends Comparable<? super T>> — allows subclasses
  • CRTP (Curiously Recurring Template Pattern) — pattern from C++, adapted for Java
  • Type erasure: T extends Comparable<T> -> erasure = Comparable

Frequent follow-up questions:

  • Why is recursive bound needed? — Guarantees type-safe comparison: T compares with T, not with Object
  • Why does Enum use recursive bound? — So that Color.compareTo(Color) accepts Color, not Enum
  • What is relaxed recursive bound?? super T allows subclasses to inherit Comparable
  • Where is self-bounding used? — Fluent builder pattern: withName().withAge().build()

Red flags (DO NOT say):

  • ❌ “Recursive bound creates infinite recursion” — It’s a compile-time constraint, not runtime
  • ❌ “Enum<E extends Enum> is a Java quirk" — It guarantees type-safe compareTo
  • ❌ “Self-bounding is only for builders” — Also for graph nodes, entities, comparables
  • ❌ “Relaxed bound is less safe” — ? super T is safer, allows more types

Related topics:

  • [[11. What are Generics in Java]]
  • [[15. What are bounded type parameters]]
  • [[17. What is PECS (Producer Extends Consumer Super)]]
  • [[26. Can you use multiple bounds for a single type parameter]]