A lost-property desk stores every item as one universal supertype and casts on collection — what does a type parameter buy instead?
answer
- the deposit knew more than the store
- every retrieval carries a cast
- who finds the wrong item, and when
- collection time versus compile time
- one body the compiler can check
basics
~20 sA type parameter moves the wrong-kind failure from collection time to the deposit call, and from run time to compile time. Callers also get items back already typed, so no downcast on retrieval can fail.
solid answer
~40 sStoring everything as a universal supertype throws away what the caller knew at the moment of deposit. The code compiles, but every retrieval has to downcast, and a wrong deposit is only noticed when someone collects that item — a different line, in different code, from the one that made the mistake. Declaring the desk over a placeholder `T` keeps that knowledge in the signature: `deposit(tag, item: T)` rejects the wrong kind at the call site, `collect(tag): T` hands back something already typed, and no cast appears in caller code at all. You pay a denser signature and you must decide what `T` is at each use. What you get is one implementation the compiler checks once, in terms of `T`, instead of one implementation it cannot check at all.
code
pseudocode · 11 lines// design A: one desk, every item stored as the top type
deskA = Store()
deskA.deposit("t1", umbrella)
deskA.deposit("t2", ticketStub) // accepted: everything is an item
u = downcast(deskA.collect("t2"), Umbrella) // FAILS here, at collection
// design B: one desk, the kind left as a placeholder
deskB = Store<Umbrella>()
deskB.deposit("t1", umbrella)
deskB.deposit("t2", ticketStub) // REJECTED here, at the deposit
u = deskB.collect("t1") // already an umbrella, no downcastgo deeper
Recall the two costs of storing everything as one universal supertype: every retrieval needs a downcast, and a wrong deposit is not noticed until someone collects it. Say what the placeholder replaces those with.
Explain the mechanics: the deposit parameter's type is what makes the call site checkable, and the collect return type is what removes the cast. Point out that the shared body is checked once, in terms of the placeholder, for every kind at once.
Frame it as failure reporting. The interesting property is that the mistake is reported at the line that made it rather than at whoever collected, which is what changes debugging time in a real system.
The trade you own is where the typed boundary sits. Decide which entry points are checked and where mixed content is genuinely required, because a desk that must hold anything reintroduces the downcast and should do so deliberately, not by accident.
## Three ways to write one desk A lost-property desk does three things — take an item in against a tag, hold it, hand it back — and none of them depend on what the item *is*. The tag index, the expiry sweep and the hand-back are identical whether the desk holds umbrellas, laptops or coats. Only the **kind of thing held** varies. Three designs are possible, and the interview question is why the third is not merely the second with extra punctuation. - **One desk per kind.** Copy the whole store and change the item type in the field, the deposit parameter and the collect return. Three bodies. - **One desk over a universal supertype.** Declare the held field as the **top type** — the type every value in the language belongs to. One body, but the desk now knows nothing about what it holds. - **One desk with a placeholder.** Declare the desk over a **type parameter** `T` and use `T` for the field, the deposit parameter and the collect return. One body, and each *use* of the desk fixes `T` to one concrete kind. ## What the top-type desk gives away At the deposit call the calling code knows exactly what it is handing over. The top-typed desk refuses to record that fact: its parameter accepts anything, so the knowledge dies at the door. Two consequences follow, and both are about **when and where a mistake is reported**. 1. **Retrieval needs a downcast.** `collect(tag)` can only promise "an item". The caller asserts the kind it expects — and that assertion is a *promise by the programmer*, not a fact the compiler established. If the promise is wrong, the only way to find out is to run it. 2. **A wrong deposit is invisible.** Handing a ticket stub to an umbrella desk compiles and runs perfectly. Nothing appears wrong until someone collects that tag, and the failure then surfaces on the collector's line — different function, different call stack, possibly a different team's request. Candidates usually undersell the second point. The downcast does not *cause* the failure; it is only the first place the mismatch can be noticed. The defect was the deposit, and this design has no way to point back at it. ## What the placeholder puts back A desk declared as `Store<T>`, with `deposit(tag, item: T)` and `collect(tag): T`, restates in the signature the thing the caller already knew: - the **deposit call is checked** against that desk's own kind, so the ticket stub is rejected on the line that offered it, before the program runs; - the **collect call needs no cast**, because its result type is `T`, and at that use site `T` *is* the concrete kind; - there is still **one body**, which the compiler checks once in terms of `T`, and that single check covers every kind anyone will ever instantiate it with. That last point is the one people forget. Copy-pasting per kind also gives you checking — three copies are each checked — but at the price of three bodies to fix. The top type gives one body at the price of the checking. The placeholder is the only one of the three that gives both at once. ## Side by side | | desk per kind | one desk, top type | one desk, placeholder | |---|---|---|---| | bodies to maintain | three | one | one | | wrong kind reported | at the deposit call | at collection, when the downcast runs | at the deposit call | | caller's retrieval code | already the right kind | must downcast | already the right kind | | adding a fourth kind | another full copy | nothing to change | one new use site | | what the compiler verifies | each copy separately | that the item is *something* | the one body, for every `T` | ## What it costs It is not free, and an interviewer likes hearing the cost named: 1. The **signature is denser** to read, and the reader has to hold a placeholder in their head while reading the body. 2. The caller must **decide the kind at each use**. A desk that genuinely has to hold mixed items is not this design; widening to a shared supertype there brings the downcast back, honestly and on purpose. 3. The guarantee is a **compile-time** one, established at the typed entry points. A path that reaches the stored items without going through those entry points is outside what the placeholder can promise. ## The two adjacent claims that are wrong The placeholder is not a **performance** feature: how the shared body is compiled is a separate decision, and the deposits and collections themselves cost what they cost either way. And removing casts is not a matter of tidiness — the cast's real price is not the characters it takes, it is that the failure gets reported at the wrong end of the program, by the innocent party.
- If a desk is only ever used with one kind of item, why not just write that kind into it directly?You should. A concrete type is the right answer for one kind — it is shorter to read and checks just as well. The placeholder earns its keep from the second use: two or more kinds, or a caller that chooses the kind, is what turns a concrete desk into a copy-paste problem.
- The desk also needs a method that lists everything it currently holds. What does that method return in each design?In the top-typed design it returns a collection of top-typed values, and the caller downcasts every element it wants to use — the same failure, now once per element. In the parameterized design it returns a collection of `T`, so each element arrives as the concrete kind and the loop body needs no cast.
saying these in an interview costs you the question
- Casting is cheap, so the two designs are basically equivalent
- A type parameter is a performance feature that avoids conversions
- The wrong deposit fails at the deposit, so it is caught early anyway
- A universal supertype is fine because we only store one kind in practice
- Placeholders exist mainly to save typing in the declaration