skip to content

Parametric Polymorphism

Reading a signature as a claim about every possible type, what that claim alone guarantees, and abstracting over a container rather than its element. Interviewers test reasoning from a signature.

on this pageshow

questions

13

A routine takes one value of an unknown type and returns a value of that same type — what can its body return?

level: middleimportance: must knowfreq 50%

answer

  1. the signature is the whole argument
  2. count what is reachable of that type
  3. nothing to inspect, nothing to construct
  4. the argument is the only material
  5. hand it back, or never return

basics

~20 s

Only the value it was handed. A body that can neither inspect nor construct a value of an unknown type has no other value of that type in scope, so the one body that returns a result returns its argument.

solid answer

~50 s

Read the signature as a claim about every type at once: the caller supplies the type, the body is written once and is never told which type arrived, and it is handed no operation on that type. So it cannot build a fresh value of it, cannot produce a default for it, and cannot derive one from anything else in scope — every other value it can reach has a type the body names. The argument is the only material of that type available, so the one body that returns a result is the one that returns its argument. The caveats matter as much as the conclusion: the signature says nothing about whether the routine returns at all, nothing about effects along the way, and the whole argument assumes the body genuinely cannot ask what the type is.

code

pseudocode · 8 lines
pseudocode
// signature: pick(value: T) -> T, for any T the caller chooses
function pick(value)
    return value          // the only value of type T in scope

// not available inside this body:
//   make_new_T()        nothing tells it how to build one
//   default_of_T()      the signature grants no default
//   compare(value, x)   no operation on T was handed over

go deeper

for a junior

Recall the shape: one value in, the same type out, and no way to look at it or make another. The answer is that the argument comes back, and being able to say why is enough at this stage.

for a middle

Explain the mechanics: enumerate what values of that type are reachable inside the body and show that the list has exactly one entry. Then state the caveats — termination and effects are not covered by the signature.

for a senior

Show that you use this in review. Demonstrate how a narrow signature lets you approve a call site without reading the body, and name the conditions under which you would stop trusting it.

for a principal

Frame the trade-off: narrowness bought by refusing to name a type is review capacity that scales across teams, paid for with call sites that must supply operations explicitly rather than assuming them.

## The claim a signature over an unnamed type makes A routine whose input is one value of a type it does not name — written below as the placeholder `T` — is not one routine per type. It is a **single body that must work for every `T` a caller can choose**. The caller does the choosing; the body was written and compiled before any choice existed, and nothing at run time tells it which choice arrived. **Parametricity** is the name for what follows: because the body cannot look at `T`, its behaviour cannot depend on `T`. That turns the signature from a label into a small proof, and the proof is what makes this an interview question rather than a definition. ## Counting the values of that type in scope The argument is mechanical. Standing inside the body, ask how many values of type `T` you can get hold of: - **The argument.** That is one. - **A freshly built value.** Not available — building one needs a constructor, a literal form or a representation, and the body has none of these for a type it does not name. - **A default, a zero, an empty.** Not available — "the default for `T`" is not something the signature hands over, and many types have no sensible one. - **A value read from elsewhere** — a field, a shared table, a collection. Anything reachable that way has a type the body *does* name, so it is not a `T`. - **A modified copy of the argument.** Modifying needs an operation on `T`, and the signature grants none. One value is reachable, and the result must have that type. Hence the body that returns a result returns its argument. ## What the proof does not cover This is where a careless statement of the rule fails, and interviewers listen for it: 1. **It does not prove the routine returns.** A body may loop forever, or fail. The type says nothing about termination. 2. **It does not prove nothing else happens.** Where effects are permitted anywhere, the body may write a log line, mutate something it reaches, or raise, and still honour the signature. 3. **It does not prove the type is opaque.** Some run times discard the type argument before execution; others keep it available and let the body ask what it was. Where the body can ask, it can branch on the answer, and the whole argument collapses. That is a property of the platform, not of the notation. Say the caveats out loud. A candidate who claims the signature proves "it always returns its argument, no exceptions" has over-claimed, and over-claiming is exactly what the question probes. ## The same argument at other shapes The reasoning is not special to one value in and one value out. It is a count of what the body can reach, and it scales: | Signature over an unnamed type `T` | Bodies that can return a result | |---|---| | one `T` in, one `T` out | exactly one — hand back the argument | | two `T`s in, one `T` out | exactly two — the first, or the second | | one `T` in, a list of `T` out | one per output length — the argument repeated *n* times, for any *n* at or above zero | | one `T` in, a number out | many — the result does not involve `T`, so the body is unconstrained | | one `T` in, nothing out | one observable behaviour — there is nothing to report | Every row assumes the same three conditions: the body is **total**, **effect-free**, and **cannot inspect** the type. Drop any one of them and the count in the right-hand column stops being an upper bound. ## Why this is worth an interviewer's time It separates two ways of reading a signature. One candidate reads it as documentation — a hint about intent that the body may or may not honour. The other reads it as a **constraint the compiler enforces**, and can therefore say what a call does without opening the body at all. The second reading is what makes uniform code cheap to review: a reviewer meeting an unfamiliar call site gets the answer from the declaration, and gets it for every type argument that will ever be supplied, including ones nobody has written yet. The practical habit that follows is to reach for an unnamed type **deliberately**, because the narrowness is the feature. A routine that names a concrete type has as many possible behaviours as its author had ideas; a routine over an unnamed type has, quite literally, one.

  • What changes about that conclusion if the body is allowed to run effects before returning?
    The conclusion about the returned *value* survives — there is still only one value of that type in scope. What is lost is the claim that two calls are interchangeable: the body may log, mutate something it reaches, or fail partway, so replacing a call with its argument is no longer a safe rewrite even though the result would match.
  • A routine takes one value of an unknown type and returns a list of that type — how many bodies now?
    One for each output length. Every entry in the result must be the single argument, so a body is fixed by how many copies it emits: none, one, two, and so on. The count is infinite but still tiny in shape — the body chooses a number, never a value.
  • Does the argument still hold if the routine takes two values of the same unknown type?
    Yes, with the count raised from one to two. Both arguments have the required type, so a body may return either, but it has no way to choose between them based on what they are — it can only pick a position, and it picks the same position on every call.

A courier is handed one sealed, unlabelled parcel and told to hand back a parcel of the same kind, with nowhere to buy another and no way to open this one. The only parcel they can hand back is the one they are holding — though nothing stops them dawdling forever instead, which is why the rule promises a value, not a delivery.

saying these in an interview costs you the question

  • Says the body could return a default value of that type
  • Claims it can construct a fresh value of the unknown type
  • Thinks the body may compare the value against something
  • Says the signature proves the routine always returns
  • Thinks the body is compiled once per type argument it meets
open as a page

Why can a relay body that carries any caller-chosen payload type not branch on which type it was handed?

level: middleimportance: must knowfreq 55%

basics

~20 s

Because the signature promised the same behaviour for every type a caller may name, and behaviour that depends on the choice makes that promise false for the rest. An unconstrained placeholder also declares no capability, so there is usually nothing to branch on.

open as a page

In a relay that carries a payload of any type the caller names, who fixes that type, and when?

level: middleimportance: must knowfreq 62%

basics

~20 s

The caller fixes it, independently at every call site. A universally quantified signature promises the same behaviour for every type a caller may name, so the implementation is written once and has to hold for all of them.

open as a page

A validation helper must work for any result wrapper rather than any element type — what must its type parameter itself accept?

level: middleimportance: should knowfreq 46%

basics

~20 s

Its type parameter must itself take a type argument: it stands for a wrapper, not for a finished type. Such a parameter is higher-kinded, and its kind records how many arguments it still expects before it names a type.

open as a page

When a type system's placeholders cannot stand for a wrapper itself, what do teams write instead of one pipeline per wrapper?

level: seniorimportance: should knowfreq 40%

basics

~20 s

They normalise every stage result onto one chosen wrapper at the boundary, encode the wrapper as a marker type with lift and lower conversions, or generate the per-wrapper copies from a single source. Each trades the missing abstraction for a different cost.

open as a page

A routine's only input is a list of entries of an unknown type and it returns a list of that type — what bodies remain possible?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Only bodies that choose by position. Every output entry must be one the caller supplied, so the body is fixed by the input's length alone — it may reorder, drop or duplicate entries, but never inspect, compare or invent one.

open as a page

During review you find a run-time type test inside a routine declared over an unknown entry type — what reasoning does it destroy?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Every conclusion that came from the declaration alone. Once the body can ask what the type is, the guarantee stops being a proof the type system enforces and becomes a promise a reader must check, for each type argument.

open as a page

When a relay hands back a payload whose type it picked itself rather than one the caller named, what changes?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The direction of the promise flips. A caller-chosen parameter obliges the implementation to work for every type; an implementation-chosen one obliges the caller to work with whatever came back, using only the operations published alongside it.

open as a page

Your team duplicates a validation pipeline per result wrapper — when is making it wrapper-generic the wrong call?

level: principalimportance: should knowfreq 32%

basics

~20 s

When the wrapper count is small and stable, when the platform forces an encoding whose diagnostics and inference costs land on every reader, or when normalising at the boundary would have removed the duplication for a fraction of the price.

open as a page

What standard would you set for a shared library on whether routines declared over an unknown type may inspect it?

level: principalimportance: should knowfreq 28%

basics

~20 s

Default to no inspection, and make each exception a named, documented, tested contract. A declaration that proves its own behaviour costs reviewers nothing; one that permits inspection turns every future type argument into a path someone has to verify by hand.

open as a page

A shared relay must now treat some payloads specially: do you let its body inspect the payload, or require callers to supply the operation?

level: principalimportance: should knowfreq 32%

basics

~20 s

Take the operation from the caller. That keeps the signature's promise to every call site already relying on it and puts per-type knowledge with the team that owns the type. Inspection inside the body buys convenience once and charges for it permanently.

open as a page

Given a type parameter standing for any wrapper, what can a pipeline body do with a wrapped value before a constraint names the wrapper's operations?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

Almost nothing: it can pass the wrapped value along or store it, but it cannot look inside or build a differently parameterised one. Producing a result in the same wrapper requires the signature to demand an operation on that wrapper.

open as a page

Why does transforming every entry before a routine that picks entries without inspecting them equal transforming after it?

level: seniorimportance: nice to knowfreq 26%

basics

~20 s

Because the routine's choice depends only on positions, and transforming entries one at a time leaves positions alone. It takes the same slots either way, so the two orders give the same list — a theorem read straight off the signature.

open as a page