skip to content

In a TypeScript conditional type, what does the `infer` keyword do, where may the variable it declares be used, and how would you write an `ElementType<T>` that pulls the element type out of an array type?

level: middleimportance: must knowfreq 62%

answer

  1. pattern match, not a value check
  2. only inside an extends clause
  3. names a slot in the matched shape
  4. in scope in the true branch only
  5. false branch chooses the fallback

basics

~20 s

infer declares a type variable inside the extends clause of a conditional type, letting the compiler pattern-match a shape and capture whatever sits in that slot. The captured variable is in scope only in the true branch.

solid answer

~50 s

`infer` is TypeScript's pattern-matching binder. Inside the `extends` clause of a conditional type you write `infer U` at the position you care about; if the checked type matches that shape, the compiler binds `U` to whatever occupied that slot. So `type ElementType<T> = T extends readonly (infer U)[] ? U : never` reads as "if `T` is an array of something, hand me that something". Two rules matter. First, `infer` is legal only in the `extends` clause of a conditional type, nowhere else in a type expression. Second, the variable is in scope only in the true branch, because the false branch is exactly the case where nothing matched; that branch is where you choose the fallback, `never` to signal failure or `T` itself to pass the input through unchanged. All of it is compile-time destructuring and nothing is emitted.

code

typescript · 8 lines
typescript
type ElementType<T> = T extends readonly (infer U)[] ? U : never;

type A = ElementType<string[]>;            // string
type B = ElementType<readonly number[][]>; // number[]
type C = ElementType<string>;              // never

const first: ElementType<Date[]> = new Date();
console.log(first.getTime());

go deeper

for a junior

Be able to read a conditional type that contains infer and say in plain words what it extracts, and remember that it is compile-time only — no code is generated for it.

for a middle

You should write ElementType and a one-level Promise unwrapper from scratch, and state the two rules precisely: infer is legal only in an extends clause, and its variable is usable only in the true branch.

for a senior

Show judgment about the false branch — when a mismatch should pass the input through, when it should collapse to never, and how each choice affects the errors your teammates see downstream.

for a principal

Own the question of how much of a codebase's type surface should be derived by extraction at all, weighing the leverage of a single source of truth against opaque errors and slower checking for everyone.

## The problem `infer` solves A conditional type `T extends U ? X : Y` asks a yes/no question: is `T` assignable to `U`? On its own that only picks a branch. Usually what you want is a *piece* of the type you just matched: the element type of an array, the value a promise resolves to, the type a callback receives. `infer` is the mechanism for that. It declares a temporary type variable at a position inside the pattern, and if the match succeeds the compiler fills that variable in with whatever it found there. Think of it as destructuring for types. In JavaScript you write `const [first] = arr` to name a piece of a value. In TypeScript's type layer you write `T extends [infer First, ...unknown[]] ? First : never` to name a piece of a *type*. ## Writing the canonical extractor ```ts type ElementType<T> = T extends readonly (infer U)[] ? U : never; type A = ElementType<string[]>; // string type B = ElementType<number[][]>; // number[] type C = ElementType<string>; // never ``` The parentheses in `(infer U)[]` matter: they bind the inference to the *element* slot of an array type. Writing `readonly` in the pattern is a small but useful trick — a mutable `string[]` is assignable to `readonly string[]`, so the readonly form matches both mutable and readonly arrays, while `T extends (infer U)[]` alone rejects a `readonly string[]` input. `Array<infer U>` is an equivalent spelling of the same pattern. ## The two scope rules **`infer` only lives in an `extends` clause of a conditional type.** You cannot write `type Bad<T> = infer U;` or put `infer` on the left of `extends`. The compiler rejects it with a message saying `infer` declarations are only permitted in the extends clause of a conditional type. It is not a general "give me a fresh type variable" keyword; it is part of the matching syntax. **The variable exists only in the true branch.** `type Bad<T> = T extends (infer U)[] ? number : U;` fails with a cannot-find-name error on the `U` in the false branch. That is not an arbitrary restriction: the false branch is reached precisely when the pattern did not match, so there is nothing for `U` to be. ## Choosing the false branch The false branch is a design decision, and interviewers listen for it. ```ts type Unwrap<T> = T extends Promise<infer U> ? U : T; // pass-through type StrictUnwrap<T> = T extends Promise<infer U> ? U : never; // fail loudly ``` `T` as the fallback makes the helper idempotent and forgiving — `Unwrap<string>` is `string`. `never` makes a mismatch visible: the result becomes uninhabitable, and downstream code that tries to use it usually errors. A third option is a sentinel object type, which produces friendlier errors than a bare `never`. Returning `unknown` is the least useful choice, because it silently swallows the failure and forces callers to narrow. ## Multiple `infer` variables One `extends` clause may declare several, with different names: ```ts type Split<T> = T extends { data: infer D; error: infer E } ? [D, E] : never; type R = Split<{ data: string; error: Error }>; // [string, Error] ``` Each name is bound independently. Reusing the *same* name at two positions is legal too, but it changes the semantics — the compiler then has to combine several candidates into one type, which is a separate topic. ## Nothing here exists at runtime This is the point the whole type layer sits on. `ElementType` is not a function; it produces no JavaScript. There is no runtime array to inspect, no check performed, no cost paid. If a candidate says "infer looks at the value and works out its type", they have confused the type layer with reflection. The compiler is matching *declared* types against a pattern during checking, then erasing the entire apparatus. A corollary: because the matching is purely structural and compile-time, `infer` can only recover what the type system was told. If a value arrives as `any` or is asserted with `as`, the extraction faithfully propagates whatever fiction was declared. ## What interviewers are really testing They want to see you write a small extractor on a whiteboard without hesitating: name the conditional, put `infer` in the right slot, pick a sensible false branch, and say out loud that the variable is true-branch-only. Getting `ElementType` and a one-level promise unwrapper right, and explaining why the false branch matters, covers almost everything this question is asked to reveal.

  • Why does the compiler refuse to let you reference the inferred variable in the conditional's false branch?
    Because the false branch is reached exactly when the pattern failed to match, so there is no candidate to bind. Referencing it there is a cannot-find-name error, not a subtle typing issue. If you need a value in both branches, put the shared type in the alias's own type parameters instead of trying to smuggle the inference out.
  • Your extraction helper returns `never` for an input you expected it to match. How do you find out why?
    Check the pattern against the input's *declared* type, not the value you have in mind. Common causes: a `readonly` array matched against a mutable pattern, an optional property making the shape non-assignable, or a wider annotation upstream. Temporarily replacing the false branch with the input `T` shows you what actually arrived at the conditional.
  • Does an infer-based helper cost anything at runtime?
    Nothing. Type aliases, conditional types and `infer` are erased during compilation — no code is emitted for them and no check is performed. The only cost is compile-time: the checker does the matching work, so very large or deeply nested helpers slow down type checking and editor responsiveness, never the shipped program.

saying these in an interview costs you the question

  • Says infer inspects a value at runtime to find its type
  • Believes infer can appear anywhere in a type alias
  • Uses the inferred variable in the false branch
  • Confuses infer with a type parameter the caller passes in
  • Assumes an extraction helper returns a value rather than a type

context