A validation helper must work for any result wrapper rather than any element type — what must its type parameter itself accept?
answer
- abstract over the wrapper, not the element
- the parameter is not yet a type
- arity of a type constructor
- kinds: star, star to star
- W<A> in, W<B> out
basics
~20 sIts 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.
solid answer
~40 sAn element-generic signature says "for every element type `A`" and keeps the container fixed. A wrapper-generic one has to say "for every wrapper `W` that still takes one element type", so `W` on its own is not a type — only `W<A>` is. A parameter like that is **higher-kinded**, and its *kind* is an arity description: a finished type has kind `*`, a one-argument wrapper `* -> *`, a two-argument wrapper `* -> * -> *`. The pipeline signature then reads `runStage<W, A, B>(input: W<A>, step: A -> B) -> W<B>`, and one body serves a maybe-present result, a list of results and a success-or-failure result. Kind checking is the compiler's arity check one level up: applying `W` to the wrong number of arguments is a kind error, caught before the program runs.
code
pseudocode · 10 lines// A: the ELEMENT is open, the container is fixed
function firstOrDefault<A>(items: List<A>, fallback: A) -> A
// B: the WRAPPER is open — W is not a type, only W<A> is
function runStage<W<_>, A, B>(input: W<A>, step: function(A) -> B) -> W<B>
// B serves all three call shapes with one body:
// runStage(maybePresentName, trim) -> maybe-present cleaned name
// runStage(listOfAddresses, normalise) -> list of normalised addresses
// runStage(parsedOrFailed, toDomain) -> success-or-failure domain valuego deeper
Know the shorter fact first: a type parameter normally stands for a finished type, the kind of thing a variable can hold. Everything here is about a parameter that does not yet stand for one.
Be able to read arity off a signature and say why the bare wrapper parameter is not a type. Explain kind * versus * -> * and give one signature that needs the second.
Show where this bites in a real codebase: the pipeline that got copied once per wrapper because the return type could not re-parameterise the caller's wrapper. Say which part of the signature is unwritable, not just that it is awkward.
Frame it as an expressiveness budget. Abstracting over wrappers puts an extra parameter in every signature on that path, so weigh the abstraction against the copies it removes and against what the platform supports natively.
## Two different things a signature can leave open Most generic code leaves the **element** open and nails the **container** down. A helper declared as `firstOrDefault<A>(items: List<A>, fallback: A) -> A` works for every element type, but it is welded to one container: hand it a maybe-present result instead of a list and the signature no longer applies. The parameter `A` stands for a *finished* type — something a value can actually have. Some code wants the opposite freedom. A validation pipeline runs the same stages whether a stage result arrives as a **maybe-present value**, a **list of values**, or a **success-or-failure value**. What varies is not the element but the wrapper around it. To write that pipeline once, the signature must leave the wrapper open: - element-generic: `firstOrDefault<A>(items: List<A>, fallback: A) -> A` — `List` fixed, `A` varies - wrapper-generic: `runStage<W, A, B>(input: W<A>, step: A -> B) -> W<B>` — `W` varies, and `W` is not a type The second line is this whole subject. `W` alone names nothing you can hold a value of; only `W<A>` does. A parameter like that is called **higher-kinded**. ## Kinds: arity, one level up A **kind** classifies types the way a type classifies values. It records one thing: how many type arguments something still needs before it becomes a type you can give to a value. | What it is | Kind | What it can stand for | |---|---|---| | a finished type | `*` | the type of a whole number; a list of text values | | a one-argument constructor | `* -> *` | a maybe-present wrapper; a list; a lazy computation | | a two-argument constructor | `* -> * -> *` | a success-or-failure wrapper; a key/value mapping | | a constructor taking a constructor | `(* -> *) -> *` | a wrapper parameterised by another wrapper | A parameter whose kind is anything other than `*` is higher-kinded. **Kind checking happens at compile time**, exactly like type checking: writing `W<A, B>` where `W` was declared to take one argument is a kind error, the same class of mistake as calling a one-parameter function with two arguments. Nothing about kinds survives into the running program — they are a static arity discipline, not run-time data. ## What changes when the wrapper becomes the parameter 1. `W` may no longer appear as the type of a value, a field or a return — only `W<something>` may. The parameter is applied or it is nothing. 2. The body loses every operation it had on the fixed container. A body that knew it held a list could iterate; a body that holds `W<A>` for an unknown `W` cannot, until the signature demands an operation. 3. The caller now chooses two things instead of one: the element type *and* the wrapper. That is a strictly stronger promise from the implementation. 4. The return type can re-parameterise the same wrapper — `W<B>` out for `W<A>` in — which is exactly what a single shared pipeline needs and what an element-only parameter cannot express. ## Partial application, and where systems stop short A success-or-failure wrapper takes two arguments: the failure type and the success type. Where a `* -> *` parameter is expected, one of those has to be fixed, leaving a constructor of the needed arity. Type systems differ sharply here, and the honest answer names the difference rather than a product: some allow a constructor to be partially applied or an inline type-level function to be written at the use site; some allow only the trailing argument to be left open, so the argument order in the declaration decides what is expressible; and some have no parameters of kind `* -> *` at all, which means the wrapper-generic signature simply cannot be written and the duplication has to be handled another way. ## Three things this is not - **Not higher-rank.** Rank-N polymorphism is about *where the quantifier sits* — a function argument that is itself generic. Higher-kinded is about *what a parameter stands for*. The two are independent axes, and swapping them is the classic confusion in this material. - **Not a bound.** A constraint narrows *which* types may be substituted; a kind describes *what shape* of thing the parameter is. A higher-kinded parameter can be unconstrained, and a constrained parameter can have kind `*`. - **Not reification or erasure.** Whether type arguments survive to run time is a separate question. Kinds are checked and discarded by the compiler either way. In an interview the tell is whether you can say the sentence "`W` is not a type until it is applied" and then read the arity off a signature without reaching for a keyword.
- A success-or-failure wrapper takes two type arguments. How can it be used where a one-argument wrapper is expected?By fixing one argument and leaving the other open, so the partially applied constructor has the needed arity — the failure type is pinned and the success type stays free. Type systems differ in how far they go: some allow partial application or an inline type-level function at the use site, some allow only the trailing argument to be left open, and some cannot express it at all.
- Is a parameter constrained to an interface whose methods are generic the same thing as a higher-kinded parameter?No. That parameter still has kind `*` — it stands for one finished type that happens to carry generic methods. A higher-kinded parameter is not a type until applied. The practical difference shows in the return type: only the higher-kinded form can say `W<B>` for the same `W` the caller passed in as `W<A>`.
A recipe that works for any ingredient is element-generic; a recipe that works for any cooking vessel — pot, pan, oven dish — has to name the vessel as the thing that varies, and a vessel is not a meal until something is put in it.
saying these in an interview costs you the question
- Says every generic type parameter is already higher-kinded.
- Thinks the bare wrapper parameter names a type a value can have.
- Calls a parameter higher-kinded because its element type is itself generic.
- Confuses it with a quantifier nested inside an argument type.
- Believes kind arity is checked at run time rather than by the compiler.