What is Type Erasure?
Type erasure explained — why List<String> and List<Integer> share one class, its consequences, and why Java chose it over reified generics.
Expected Interview Answer
Type erasure is the mechanism by which the Java compiler enforces generic type safety at compile time and then removes (erases) all generic type parameter information from the compiled bytecode, replacing each type parameter with its bound (usually Object) so that only one compiled class exists for every type argument.
When you write `List<String>` and `List<Integer>`, both compile down to the exact same bytecode for `List` — the JVM has no idea at runtime which type argument was used, because `List<String>` and `List<Integer>` share a single `.class` file. The compiler inserts the necessary casts automatically at every read site (a hidden `(String) list.get(0)`) so the program still behaves correctly, but that safety net exists only at compile time. This design choice was made for backward compatibility: generics were added in Java 5, and erasure let generic code interoperate with pre-existing non-generic bytecode without breaking it. The consequences are real interview traps: you cannot do `new T()` or `new T[]` (no runtime type to instantiate), you cannot overload two methods that differ only by generic type argument (`foo(List<String>)` vs `foo(List<Integer>)` collide after erasure), and `instanceof` cannot check a parameterized type (`obj instanceof List<String>` is illegal — only the raw `obj instanceof List` is allowed).
- Preserved backward compatibility with pre-generics Java bytecode and libraries
- Kept the JVM specification simple — no new runtime type-parameter machinery was needed
- Still delivers full compile-time type safety, catching mismatches before they run
- Avoids the code-size blowup C++ template instantiation can cause, since only one class exists per generic type
AI Mentor Explanation
A scorer’s handwritten notes distinguish “over bowled by a spinner” versus “over bowled by a pacer” while writing the match report, carefully labeling the bowler type as they go — but once the report is typeset into the final printed scorecard, only "Over 14: 6 runs" survives; the bowler-type label is gone from the archived record. The type distinction existed only during drafting, not in the final artifact. Type erasure works the same way: the compiler checks and uses generic type information while compiling, then discards it from the final bytecode.
Step-by-Step Explanation
Step 1
Compiler checks generic usage
While compiling, the compiler verifies every use of a type parameter against its declared or bounded type.
Step 2
Compiler inserts casts
At each point a generic value is read out, the compiler inserts the equivalent cast automatically (e.g. (String) list.get(0)).
Step 3
Type parameter erased
In the compiled bytecode, T is replaced by its bound (Object if unbounded), leaving a single raw class definition.
Step 4
JVM runs without generic info
At runtime, the JVM only sees the erased raw types — reflection cannot recover the original type arguments used at a call site.
What Interviewer Expects
- A correct definition: generics checked at compile time, erased in compiled bytecode
- At least one concrete consequence — cannot do new T(), no overload on generic type alone, no instanceof on parameterized type
- Understanding of why Java chose this design (backward compatibility with pre-Java-5 bytecode)
- Awareness that List<String>.class and List<Integer>.class are the exact same Class object
Common Mistakes
- Claiming Java generics behave like C++ templates with per-type compiled code
- Believing you can safely check obj instanceof List<String> at runtime
- Not knowing why new T() is illegal inside a generic class
- Forgetting that two overloads differing only by generic type argument cause a compile error, not a runtime ambiguity
Best Answer (HR Friendly)
“Type erasure means Java checks your generic types while compiling the code, but then throws that type information away in the final compiled bytecode, so at runtime a List of Strings and a List of Integers are actually the exact same class. This kept generics backward compatible with older Java code, but it explains some surprising limitations, like not being able to create a new instance of a generic type parameter directly.”
Code Example
List<String> strings = new ArrayList<>();
List<Integer> integers = new ArrayList<>();
// After erasure, both are the exact same runtime class
System.out.println(strings.getClass() == integers.getClass()); // true
class Box<T> {
// T item = new T(); // illegal - no runtime type to instantiate
// Illegal overload attempt (erased signatures collide):
// void put(List<String> l) {}
// void put(List<Integer> l) {} // compile error: same erasure
Object raw;
void set(T value) { raw = value; } // compiler inserts a cast on read
}Follow-up Questions
- Why can’t you create an array of a generic type, like new T[10]?
- Why does the compiler reject two overloaded methods that differ only by generic type argument?
- Why was type erasure chosen over reified generics in Java’s design?
- How does the compiler insert casts to preserve type safety despite erasure?
MCQ Practice
1. After type erasure, what do List<String> and List<Integer> resolve to at runtime?
Type erasure removes the type argument, so both List<String> and List<Integer> compile to the same raw List class at runtime.
2. Why is `new T()` illegal inside a generic class in Java?
Because type erasure removes T at runtime, the JVM has no concrete class to construct, so new T() is disallowed.
3. Why was type erasure chosen when Java introduced generics in Java 5?
Erasure let generic code interoperate with pre-Java-5, non-generic bytecode without breaking the existing ecosystem.
Flash Cards
Type erasure in one line? — Generic type info is checked at compile time, then removed from the compiled bytecode.
Why was it chosen? — To preserve backward compatibility with pre-generics Java bytecode.
Consequence: new T()? — Illegal — no runtime type information exists to instantiate.
Consequence: instanceof? — Cannot check obj instanceof List<String>; only the raw obj instanceof List is legal.