skip to content

Generics

Parametric polymorphism in TypeScript: writing functions, types, and classes that work over a type parameter instead of one concrete type, and getting the compiler to infer that parameter for you. Interviewers lean on generics because they separate people who can read a library's types from people who can design them.

part ofTypeScriptoverview, primer and where to startread it →
on this pageshow

explore

questions

page 2 of 2

How do you type the `ok()` and `err()` constructor helpers of a generic `Result<T, E>` union so a function can return both without annotating T and E at every call site?

level: middleimportance: should knowfreq 45%

basics

~20 s

Give each helper a type parameter only for the branch it actually fills: ok<T>(value: T): Result<T, never> and err<E>(error: E): Result<never, E>. Because never is assignable to anything, both results fit the function's declared Result type.

open as a page

A generic custom hook ends with `return [value, setValue];` and has no return type annotation. Why does the caller's destructured setValue come out with a union type, and how do you type the hook so the pair destructures correctly?

level: middleimportance: should knowfreq 42%

basics

~20 s

An array literal is inferred as an array of the union of its element types, so both destructured names get value-or-setter. Annotate the return type as a tuple, such as [T, (next: T) => void], or return the literal with as const.

open as a page

In TypeScript with strict enabled, given `class Dog extends Animal` and `interface Cell<T> { value: T; set: (next: T) => void }`, is `Cell<Dog>` assignable to `Cell<Animal>`, is `Cell<Animal>` assignable to `Cell<Dog>`, or neither — and why?

level: middleimportance: should knowfreq 40%

basics

~10 s

Neither direction is allowed: Cell<T> is invariant because T appears in both an output position (value) and an input position (set). Covariance and contravariance both apply, and only the same type argument satisfies both.

open as a page

In TypeScript, `function make<T extends { name: string }>(): T { return { name: "anon" }; }` fails with "'{ name: string; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{ name: string; }'". What is the compiler protecting against, and how do you fix it?

level: seniorimportance: should knowfreq 45%

basics

~20 s

The caller chooses T, not the function, and may choose a subtype requiring more than name. A value that merely satisfies the constraint is therefore not a valid T. Fix the signature — return the concrete type or derive the value from a T the caller passed in.

open as a page

In TypeScript, `declare function set<T, K extends keyof T>(o: T, k: K, v: T[K]): void`. If `o` is `{ a: 'x', b: 1 }` and `k` is a variable declared as `'a' | 'b'`, does `set(o, k, 1)` compile, and is it safe?

level: seniorimportance: should knowfreq 30%

basics

~20 s

It compiles but is unsafe. K infers as the union 'a' | 'b', so T[K] widens to string | number and the number 1 is accepted — even though at runtime k may be 'a', writing a number into a property typed string.

open as a page

You are designing a public TypeScript type whose declaration is shaped like `Query<TData, TError, TKey>`. How do you decide the order of the type parameters and which of them get defaults, and what does that decision commit you to later?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Order type parameters by how often callers will specify them, most-specified first, because a caller cannot skip an earlier one to reach a later one. Default the tail so the common reference stays short, and remember that only a new defaulted parameter appended at the end is non-breaking.

open as a page

When modelling a TypeScript function whose result type depends on its argument, when would you reach for a type parameter rather than writing a set of overload signatures?

level: seniorimportance: should knowfreq 37%

basics

~20 s

Use a type parameter when one uniform rule relates argument to result across an open set of types, and overloads only when a small fixed set of unrelated argument shapes produce unrelated results. Overloads also break on union arguments and on unlisted types.

open as a page

In a TypeScript API layer you find UserResponse, OrderResponse and InvoiceResponse, each repeating `status` and `requestId` and differing only in the type of `data`. How would you consolidate them into one declaration, and when is a single generic declaration the wrong answer?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Parameterize the varying slot: declare one ApiResponse<T> with status, requestId and data of type T, then use ApiResponse<User>, ApiResponse<Order> and so on. It is the wrong model when the variants differ in which fields exist, not just in one field's type.

open as a page

What problem does TypeScript's built-in `NoInfer<T>` utility type solve, and how would you use it in a generic function signature?

level: seniorimportance: should knowfreq 34%

basics

~20 s

NoInfer marks a parameter position as check-only: it stops that position from contributing an inference candidate, so the type parameter is fixed by the other arguments and a stray default or fallback argument is reported as an error instead of widening the type.

open as a page

In TypeScript, `declare function f<T extends readonly unknown[]>(arr: T): T` called as `f([1, "a"])` infers `(string | number)[]` rather than the tuple `[1, "a"]`. What signature changes make it infer a tuple, and what does each produce?

level: seniorimportance: should knowfreq 28%

basics

~20 s

An array literal only becomes a tuple when something asks for one. Make the parameter a rest parameter and TypeScript infers positionally, or add a const type parameter with a readonly array constraint to infer a readonly tuple of literal element types.

open as a page

Users of your helper `declare function pipe<A, B>(f: (a: A) => B): (a: A) => B` report that in `pipe(x => x)` the parameter `x` is `unknown`. Why does that happen, and what are your options for fixing it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

A callback parameter consumes A instead of supplying it, so nothing at the call gives A a candidate and it falls back to unknown. Annotate the parameter, add a constraint, or take the value as an argument.

open as a page

A TypeScript service has an internal helper `query<T>(sql: string): Promise<T[]>` called from hundreds of places, each passing its own row type, and several of those row types no longer match the database. Why will the compiler never report this, and how would you get the codebase back to safety?

level: seniorimportance: should knowfreq 40%

basics

~20 s

T appears only in the return type, so each call site asserts rather than proves its row type and the checker has nothing to compare it against. The fix is a signature that forces the type to be produced — returning unknown or requiring a decoder — so every unsafe site becomes a compile error.

open as a page

A TypeScript builder's `.set()` is declared to return `Builder<T & Record<K, V>>`, but at runtime it hands back the same object. What does that force the implementation to do, and what should you watch for?

level: seniorimportance: should knowfreq 30%

basics

~20 s

A value cannot change its own type parameter, so the implementation needs a type assertion to hand the object back as the new instantiation. Keep that assertion at one boundary, and remember the accumulated type is erased, so build() still validates at runtime.

open as a page

You pass a generic component `function List<T>(props: ListProps<T>): string` to a helper declared as `function wrap<P>(component: (props: P) => string): (props: P) => string`. The wrapped result no longer infers the item type at each usage. Which TypeScript rule causes that, and what is the standard fix?

level: seniorimportance: should knowfreq 30%

basics

~20 s

The helper's parameter is an ordinary, non-generic function type, so passing a generic function instantiates its type parameter once — usually to unknown — and the result is a single concrete component. The standard fix is to assert the wrapped value back to the original signature.

open as a page

Why did TypeScript's designers leave method parameters bivariant instead of checking them contravariantly like other function types, and what unsoundness does that leave in everyday code?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Contravariant method parameters would make generic types unusable: Array puts its element type in parameter positions such as push and indexOf, so an array of a subtype would stop being assignable to an array of its supertype. TypeScript traded soundness for usability.

open as a page

You are designing a shared TypeScript utility library. How do you decide how tight a generic constraint should be, and what goes wrong when a constraint is tighter than the function body actually needs?

level: principalimportance: should knowfreq 34%

basics

~20 s

Constrain to the members the body actually reads, and no more. An over-tight constraint rejects callers whose values would work perfectly at runtime, pushing them into as assertions that relocate unsoundness into their code — and loosening it later changes your published contract.

open as a page

In TypeScript, given `interface Comparable<U> { compareTo(other: U): number }`, what does the self-referential constraint in `function max<T extends Comparable<T>>(a: T, b: T): T` express, and when do you need it rather than `T extends Comparable<unknown>`?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

It requires T to be comparable against itself: any type used here must declare compareTo taking that same type. That is what makes max(a, b) meaningful, since both arguments are T and each must accept the other.

open as a page

What do the `in`, `out` and `in out` variance annotations on a TypeScript generic type parameter do, and why add them when the compiler already works variance out from usage?

level: seniorimportance: nice to knowfreq 25%

basics

~20 s

They state a type parameter's variance explicitly: out means covariant, in means contravariant, in out means invariant. TypeScript verifies the declaration against how the parameter is used, and can then relate two instantiations by their type arguments instead of comparing members.

open as a page

You maintain a widely used TypeScript library whose helpers must see the caller's literal values precisely. How do you decide between requiring a const assertion at each call site, adding `const` type parameters, and using `NoInfer` — and what does each choice cost?

level: principalimportance: nice to knowfreq 20%

basics

~20 s

Decide by who bears the burden and what it costs them. Caller-side assertions keep signatures simple but fail silently when forgotten; const type parameters guarantee precision at the price of deeply readonly types everywhere; NoInfer only fixes arguments that should conform, not infer.

open as a page

You are designing the configuration API of a TypeScript library. How do you decide between a type-accumulating fluent builder and a single typed options object, and what does the builder cost your users?

level: principalimportance: nice to knowfreq 20%

basics

~20 s

Default to a typed options object: one type, one error location, easy to serialize and extend. Reach for an accumulating builder only when later calls must depend on earlier ones — and accept worse diagnostics, slower editors, and a harder deprecation story.

open as a page

A shared `Store<T>` type in your codebase both reads and writes `T`, so it is invariant and teams keep hitting errors passing a `Store<Dog>` to code that wants a `Store<Animal>`. How do you weigh splitting it into read and write views, adding variance annotations, and letting callers cast?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

Split the type: give the reading half a parameter that appears only in output positions and the writing half one that appears only in input positions, then have APIs ask for the view they use. Variance annotations only assert existing variance, and casts hide the hazard.

open as a page

You own a widely used TypeScript library whose public types declare dozens of callback members. How do you decide between method shorthand and the property function-type form across that surface, and what does each choice commit you to?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

Declare consumer-supplied callbacks as property function types so parameter mismatches fail at compile time, and reserve method shorthand for container-like types whose usability depends on the looser check. The choice fixes whether future parameter widening breaks consumers loudly or fails silently at runtime.

open as a page

showing 31–52 of 52