advancedType Impl

What are the differences between erasing generics and monomorphizing them?

Whether the candidate can trade code size against runtime cost with real numbers in mind, and whether they know which choice their language made and what it forced on the rest of the design.

What a strong answer covers

  • Monomorphization emits a separate specialized copy of the generic code for each concrete type argument. Every copy sees the real layout, so field accesses are fixed offsets, values live unboxed, calls devirtualize, and the optimizer works on ordinary non-generic code. The costs are code size, compile time — Rust's and C++'s compile times are substantially this — and the fact that the generic body must be available to the compiler at every use site, which pushes implementation into headers or crate metadata and makes stable ABIs for generic code hard.
  • Erasure emits one copy that works for every type argument by representing all of them uniformly, usually as a pointer. The code is compact, compiles once, and a generic API can be a binary boundary. The costs are boxing for primitives, indirection on every access, no specialization for the optimizer, and the loss of the type at run time: you cannot ask what T was, so reflection, overloading on T and new T() are all unavailable or need a smuggled token.
  • There is a third answer that gets forgotten: reification without specialization, where the runtime carries the type argument as data and one code path consults it. .NET does something close for reference types — shared code — while specializing value types, which is a deliberate hybrid: the size cost is paid only where the layout benefit exists.
  • The design consequence is the interesting part. Monomorphization pushes you toward whole-program-ish builds and away from stable generic ABIs. Erasure pushes you toward a uniform value representation, which pushes you toward a garbage collector. The generics decision is not a local one.
✓ Green flags
  • Names concrete costs on both sides: code size and compile time versus boxing and indirection.
  • Knows that monomorphization requires the body at the use site, and connects that to headers, crates and ABI stability.
  • Mentions the hybrid — shared code for references, specialized for values.
  • Says what erasure makes impossible at run time, and how languages work around it.
  • Connects the choice to the rest of the language design rather than treating it as an implementation detail.
✗ Red flags
  • "Generics work the same way in every language, it is just syntax." Three different implementations with different performance, ABI and reflection consequences.
  • "Erasure is a Java mistake." It is a compatibility decision with real benefits — one copy, binary-compatible generic APIs — and .NET's reified generics have their own costs.
  • "Monomorphization is always faster." It is usually faster per call and can be slower overall once the instruction cache and the binary size are accounted for.
  • "C++ templates are the same as Java generics with the erasure removed." Templates are not even type-checked until instantiation, which is a different system with different error messages.

Follow-up

Your library exposes a generic function and you need to ship it as a shared object with a stable ABI. Which strategy can you use, and what do you give up?

Implementation challenge

What to ask them to write or trace on a whiteboard.

For fn max<T: Ord>(a: T, b: T) -> T used at i32, String and MyStruct, write down what code exists in the binary under each strategy.

The lessons behind it