skip to content

Type Erasure and Reification

What survives of a type argument at run time, what the loss forbids, and how code works around it. Interviewers want the compatibility reasoning behind discarding it, not a complaint about it.

on this pageshow

questions

16

Why does a language that discards type arguments reject a run-time test asking whether a value is a stack of text edits?

level: juniorimportance: must knowfreq 62%

answer

  1. a test reads only what survives
  2. the value, not the declaration
  3. one compiled shape per container
  4. no evidence left to compare
  5. only the argument-free test decides

basics

~20 s

A run-time test can only read what the value still carries, and the discarded argument is not there. Every stack of edits shares one run-time shape, so the test cannot be decided, and the compiler rejects it instead of guessing.

solid answer

~50 s

A run-time type test asks the value itself what it is, so it can only answer from the description the value still carries while the program runs. Where a compiler discards type arguments, the object that remains records that it is an undo stack and nothing about the kind of edit it was declared to hold: the same compiled shape backs a stack of text edits and a stack of style edits. The parameterized half of the test therefore has no evidence to consult. The compiler refuses it at compile time rather than let it produce a verdict nothing supports. The argument-free half of the same test - `is this an undo stack at all` - stays decidable, because the container shape does survive. Platforms that keep arguments available at run time allow the full test precisely because the evidence is still attached.

code

pseudocode · 6 lines
pseudocode
function replayIfTextEdits(value):
    if value is an UndoStack:            # decidable - the container shape survives
        replayLoosely(value)

    if value is an UndoStack of TextEdit:  # rejected - the argument is gone
        replayStrictly(value)

go deeper

for a junior

Remember the shape of the rule: a run-time test reads only what the value still carries, and a discarded type argument is not part of that. Do not expect an object to remember what it was declared to hold.

for a middle

Explain the mechanism rather than the rule: one compiled container shape backs every argument, so the parameterized half of the test has nothing to consult and is refused at compile time instead of returning an unsupported verdict.

for a senior

Show where this bites in real code - a boundary that receives a container and wants to branch on its element type - and how you restructure so the decision is taken where the type is still known rather than interrogated out of a value.

for a principal

The judgment to own is whether your codebase leans on element-type branching at boundaries at all. A design that asks a container what it holds is fragile under either compilation model, and standardising the boundary shape is cheaper than arguing about the test.

## What a run-time type test actually consults A **run-time type test** hands a value to the runtime and asks the value what it is. That is the whole mechanism: the answer comes from the description the value carries with it while the program runs - not from the source text that produced it, and not from the variable it happens to be sitting in. A test is decidable exactly to the extent that the carried description is complete enough to settle it. Two things must be kept apart from the start: - **The declaration** - `an undo stack whose elements are text edits` - is source text the compiler reads, checks and then throws away or keeps, depending on the platform. - **The carried description** - what the allocated stack object still says about itself at run time. In a platform that discards type arguments these two are not the same thing, and the gap between them is this whole subject. ## What is left of `stack of text edits` The declaration glues two claims together: a **container shape** (an undo stack) and a **type argument** (the kind of edit it holds). The discarding model keeps the first and drops the second. One compiled body serves every argument, so a stack declared for text edits and a stack declared for style edits allocate objects of the same run-time type. The test therefore splits in half: 1. **Is this an undo stack?** Answerable - the container shape is part of what the value carries. 2. **Are its elements text edits?** Not answerable - nothing about the value records that. The first half is not a consolation prize; it is the only part that was ever backed by evidence. ## Why the elements cannot answer it either The obvious rescue is to look inside: walk the stack and inspect what is actually stored. It does not work, and seeing why is what separates a memorised rule from a derived one. - An **empty stack** has nothing to inspect, yet it still has a declared element type, so the inspection cannot even be attempted. - The argument is a claim about **every element the stack will ever hold**, not only the ones present now. Inspecting what is stored proves nothing about the next push. - A stack that reached this code through a step nobody verified may hold a **mixture**, and a mixture has no single element type to report. - Inspection costs a walk of the whole structure, so a test that reads as a constant-time question becomes proportional to size - and still returns a guess. ## Reject, or narrow Compilers in this model do one of two things, and it is worth knowing which form you wrote. | The test as written | What it needs to decide | Verdict when arguments are discarded | |---|---|---| | Is this an undo stack? | container shape only | decidable, allowed | | Is this an undo stack of text edits? | container shape plus the argument | rejected: the evidence was discarded | | Is this an undo stack of anything, argument unread? | container shape, argument deliberately unexamined | decidable, allowed | The third row matters. Writing the test so it never asks about the argument is legal, because it only asks what the value can still answer. What you cannot do is write the second row and expect the third row's reliability. ## The undo stack at a boundary An editor exposes an extension point: a plug-in hands back an undo stack and the editor wants to replay it, but only if it is a stack of text edits. The test it wants to write is exactly the rejected one. The fix is structural, not clever: - Have the boundary accept a **declared** shape, so the check happens where the argument is still known - at compile time, at the call. - Make the distinction something the value genuinely carries, such as a **distinct container type per edit family**, so the argument-free test is enough. - Move the decision to the caller that still has the static type, instead of interrogating a value that has lost it. ## Where platforms differ This restriction is a property of a compilation strategy, not of generics as an idea. Some platforms keep type arguments available while the program runs, and there the full test is decidable and permitted. Others discard them, and there it is not. Before claiming that such a test works, name which model you are in - an engineer who says 'that check works fine' without saying which model is describing one platform and generalising it to all of them.

  • What weaker test is still decidable on the very same object?
    The one that ignores the argument: asking whether the value is an undo stack at all. The container shape is part of what the value carries at run time, so that question can be answered honestly. It simply cannot tell a stack of text edits from a stack of style edits, which is the distinction the original code wanted.
  • Why is the restriction on the test, when a cast to the same parameterized type is often accepted?
    A test must produce a truthful yes or no right now, and the evidence for the parameterized half is gone, so there is nothing to answer with. A cast is an instruction to treat the value as that type; the compiler can let it through while recording that it is no longer checking. One demands a verdict, the other only demands a description.

It is like a crate stamped only 'crate'. You can confirm it is a crate, but the label saying what it was packed for was removed before shipping, and looking at whatever happens to be inside today does not restore it.

saying these in an interview costs you the question

  • Says the object remembers the argument it was created with.
  • Claims the test works and only needs a cast afterwards.
  • Thinks the compiler substitutes the variable's declared type at run time.
  • Believes the test quietly answers false instead of being rejected.
  • Assumes every platform forbids this test, model regardless.
open as a page

A settings loader is handed the expected type as an argument alongside the key — what does it do with that argument?

level: juniorimportance: must knowfreq 55%

basics

~20 s

The type argument is the evidence the run time no longer carries: the loader uses it to choose how to convert the stored text and to check the converted value before returning it, so a mismatch fails at the read.

open as a page

After compilation on a platform that discards type arguments, what does a stored container value still record about its argument?

level: juniorimportance: must knowfreq 65%

basics

~20 s

Nothing you can ask the value for. The argument is spent during compilation, and what runs is an ordinary container over the erased element shape. Only a declaration, never the object it points at, may keep a written description of it.

open as a page

Why would a platform add parameterized types by checking the arguments and then discarding them from the compiled artifact?

level: middleimportance: must knowfreq 58%

basics

~20 s

Discarding keeps the compiled shape identical to the pre-parameterized one: one body serves every instantiation, the existing run-time system needs no change, and code compiled before the feature can still call, and be called by, parameterized code.

open as a page

Inside a generic undo stack, why can the body not construct a fresh value of its own type placeholder?

level: middleimportance: must knowfreq 55%

basics

~20 s

Construction has to name a concrete type at the point it happens: what to allocate and how to initialise it. A placeholder names none of that once the argument is discarded, and nothing promises the argument can be built at all.

open as a page

In the compiled signature of a handler declared over a restricted type placeholder, what type does that placeholder appear as?

level: middleimportance: must knowfreq 58%

basics

~10 s

It appears as its limit — the type the restriction named. An unrestricted placeholder appears instead as the platform's root reference type. The declaration alone decides the collapsed shape; call sites never influence it.

open as a page

Why do two undo-stack operations that differ only in their type argument fail to compile as separate overloads?

level: middleimportance: should knowfreq 46%

basics

~20 s

Once the argument is discarded, both operations expose the same parameter shape, so the two declarations collapse into one signature. The clash is a duplicate declaration, reported where the operations are declared rather than at any call.

open as a page

A settings loader generic over its result must produce a default for a missing key — when is a caller-supplied factory better evidence than a token?

level: middleimportance: should knowfreq 44%

basics

~20 s

A factory is evidence that can build: it covers types whose construction needs arguments or is not publicly reachable, and it keeps the loader out of reflective instantiation. A token only names a type, and building from one assumes a no-argument path exists.

open as a page

A read's signature declares a placeholder and separately takes a class token — what breaks when the two disagree?

level: middleimportance: should knowfreq 40%

basics

~20 s

Two untied statements of one type let the body narrow against the token and hand the value back as the placeholder through an unchecked step. Nothing fails at the read; the call site trusts the declared type and breaks at first use.

open as a page

When a caller reads a value out of an erased parameterized signature, what does the compiler insert, and where?

level: middleimportance: should knowfreq 46%

basics

~20 s

A checked conversion, placed in the caller's code at each site where the returned value is used at a type more specific than the collapsed one. The generic body inserts nothing; it works entirely at the erased shape.

open as a page

A decade-old rostering codebase parameterizes its collections one module at a time - what does discarding the arguments let the team avoid?

level: seniorimportance: should knowfreq 42%

basics

~20 s

It avoids a coordinated rebuild. Because the compiled shape does not change, the untyped modules already in production keep calling the parameterized ones and being called by them, so each module can be converted on its own schedule.

open as a page

What does a platform pay to keep type arguments observable at run time instead of discarding them?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Keeping the arguments forces the run-time system to model parameterization: metadata stored for each distinct instantiation, a larger and more complex runtime that understands it, and per-operation work to carry, forward and compare that metadata.

open as a page

An undo stack wants an array of its edit placeholder for fast replay, so why is creating that array directly rejected?

level: seniorimportance: should knowfreq 38%

basics

~20 s

An array keeps its element type while the program runs and guards every store against it, but the type argument is the one thing compilation discarded. The creation would have to record an element type that no longer exists, so it is refused.

open as a page

Designing generics for a new platform with no installed base, how do you decide whether type arguments survive to run time?

level: principalimportance: should knowfreq 33%

basics

~20 s

With no artifacts to stay compatible with, the strongest argument for discarding is gone, so the decision turns on the run-time bill against what library authors would otherwise pay to thread evidence by hand - and on the fact that the answer is close to irreversible.

open as a page

How can a caller pin a whole nested parameterized shape, such as a list of pairs, when a flat token cannot name one?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

Put the shape in a declaration instead of a value: declare an empty subtype whose supertype argument is the whole nested shape, hand an instance of it over, and let the loader read the shape off that supertype.

open as a page

Across platforms that differ in what they keep of a type argument, what stays observable at run time everywhere?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Only the erased shape of a value. Beyond that the platforms split three ways: the argument discarded, kept in full on the value, or kept only as a description attached to a declaration — and a description never makes a value self-describing.

open as a page