cheat sheet
infer Keyword
TypeScript's infer keyword declares a fresh type variable inside a conditional type, letting you pull a sub-type out of a matched shape. It powers ReturnType, Parameters, Awaited, and most advanced type extractors.
infer Keyword — Extracting types from patterns
What it is
infer is a single-purpose TypeScript keyword that declares a fresh type variable inside the extends clause of a conditional type. When the conditional matches, the compiler fills infer X with whatever sub-type satisfied that position; when it doesn't match, the false branch runs and X never exists. That sounds abstract, but it's the trick behind every utility type that "reads inside" another type — ReturnType<T> reads the return position, Parameters<T> reads the parameter tuple, Awaited<T> reads the resolved type of a promise, and so on.
The keyword is only legal inside a conditional type's extends position. You cannot use infer as a generic parameter or inside an interface; the compiler will reject it anywhere else. Think of it as "let me name this slot so I can refer to it on the other side of the ternary."
Install
Nothing to install. infer shipped in TypeScript 2.8 (March 2018). All examples below assume TS 5.x for the extends infer X chained syntax and recursive conditional types.
npx tsc --version
Output:
Version 5.5.4
Syntax
type Extracted<T> = T extends SomePattern<infer X> ? X : Fallback;
Output: (none — pure type syntax)
The T extends ... ? ... : ... ternary is the conditional type. The infer X introduces a brand-new type variable scoped to that conditional. If T matches the pattern, X is bound to whatever filled that slot; otherwise the false branch runs.
Essential rules
| Rule | Detail |
|---|---|
Only inside extends | infer X must appear in the extends clause of a conditional type |
| Fresh per use | Every infer X introduces a new variable, even with the same name |
| Multiple inferences | A single conditional can have many infer slots |
| Distributive over unions | Generic T extends ... distributes by default; wrap [T] extends [...] to opt out |
| Recursive | A conditional may reference itself in the true branch (recursive conditional types, TS 4.1+) |
infer X extends Y | TS 4.7+ — constrain the inferred type to a bound |
Fallback to never | When the conditional doesn't match, use never so unions filter cleanly |
The simplest example — array element
The canonical infer example: extract the element type of an array. The pattern (infer E)[] matches any array, and E is bound to the element type.
type ElementOf<T> = T extends (infer E)[] ? E : never;
type A = ElementOf<string[]>; // string
type B = ElementOf<number[][]>; // number[]
type C = ElementOf<Array<{ id: 1 }>>; // { id: 1 }
type D = ElementOf<string>; // never — not an array
Output: (none — all four are compile-time type aliases)
If you've ever wished for a way to say "give me whatever's inside an array," that's infer. The same trick works for any generic shape — Promise<infer R>, Map<infer K, infer V>, Record<string, infer V>, etc.
Built-in utilities that use infer
Almost every built-in utility type with a name like "extract" or "unwrap" is implemented with a single conditional + infer. Their definitions are short enough to memorise.
// lib.es5.d.ts (paraphrased)
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
type ConstructorParameters<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: infer P) => any ? P : never;
type InstanceType<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: any) => infer R ? R : any;
type Awaited<T> =
T extends null | undefined ? T :
T extends object & { then(onfulfilled: infer F, ...args: infer _): any } ?
F extends (value: infer V, ...args: infer _) => any ? Awaited<V> : never :
T;
type ThisParameterType<T> =
T extends (this: infer U, ...args: never) => any ? U : unknown;
Output: (none — these are declarations only)
Notice the recursion in Awaited — calling Awaited<V> inside the true branch is how nested promises get peeled.
Multiple infer slots in one conditional
A single conditional may declare more than one infer. The compiler binds each independently.
// Extract key and value types from a Map<K, V>
type MapTypes<T> = T extends Map<infer K, infer V>
? { key: K; value: V }
: never;
type R = MapTypes<Map<string, number>>;
// { key: string; value: number }
// Pull the first and second positions of a tuple
type FirstTwo<T> = T extends [infer A, infer B, ...unknown[]]
? [A, B]
: never;
type S = FirstTwo<[1, "two", true, 4]>;
// [1, "two"]
Output: (none)
Inferring function pieces — Parameters, ReturnType, FirstArg
Functions are a tuple of parameters plus a return type, and infer can read any slice of them.
type Fn = (x: number, y: string, opts: { force?: boolean }) => Promise<boolean>;
// Whole parameter tuple
type Params = Parameters<Fn>;
// [x: number, y: string, opts: { force?: boolean }]
// Just the return type
type Ret = ReturnType<Fn>;
// Promise<boolean>
// First parameter — pattern matches a head + variadic rest
type First<F> = F extends (first: infer X, ...rest: any[]) => any ? X : never;
type X = First<Fn>; // number
// Last parameter — works because tuples support variadic patterns
type Last<F> = F extends (...args: [...infer _, infer L]) => any ? L : never;
type L = Last<Fn>; // { force?: boolean }
// Parameters minus the first
type Rest<F> = F extends (first: any, ...rest: infer R) => any ? R : never;
type RestArgs = Rest<Fn>;
// [y: string, opts: { force?: boolean }]
Output: (none)
Tuple manipulation — Head, Tail, Length
Tuple types are spreadable in conditional patterns, which makes Lisp-style tuple operations natural to express with infer.
type Head<T extends unknown[]> = T extends [infer H, ...unknown[]] ? H : never;
type Tail<T extends unknown[]> = T extends [unknown, ...infer R] ? R : [];
type Last<T extends unknown[]> = T extends [...unknown[], infer L] ? L : never;
type Init<T extends unknown[]> = T extends [...infer I, unknown] ? I : [];
type T = [1, 2, 3, 4];
type H1 = Head<T>; // 1
type T1 = Tail<T>; // [2, 3, 4]
type L1 = Last<T>; // 4
type I1 = Init<T>; // [1, 2, 3]
// Length — uses the literal "length" property
type Length<T extends unknown[]> = T["length"];
type N = Length<T>; // 4
Output: (none)
infer can pattern-match around a ... spread, which is why these definitions are concise.
Recursive conditional types
A conditional may reference itself in either branch. Combined with infer, this lets you express "keep extracting until you can't anymore."
// Recursive Awaited — peel nested promises
type DeepAwaited<T> = T extends Promise<infer U> ? DeepAwaited<U> : T;
type A = DeepAwaited<Promise<Promise<Promise<string>>>>; // string
type B = DeepAwaited<number>; // number
// Flatten an arbitrarily nested array
type DeepFlatten<T> = T extends Array<infer Inner>
? DeepFlatten<Inner>
: T;
type C = DeepFlatten<string[][][]>; // string
type D = DeepFlatten<number>; // number
// Reverse a tuple
type Reverse<T extends unknown[]> = T extends [infer H, ...infer R]
? [...Reverse<R>, H]
: [];
type E = Reverse<[1, 2, 3]>; // [3, 2, 1]
Output: (none)
TS limits recursion depth — usually 50 or 100 instantiations — but that's plenty for typical use.
infer with constraints (TS 4.7+)
You can constrain an inferred type with infer X extends Y. This avoids needing a second extends check inside the true branch and lets you keep narrow literal types.
// Before 4.7 — clumsy
type FirstNum<T> = T extends [infer A, ...unknown[]]
? A extends number ? A : never
: never;
// 4.7+ — clean
type FirstNum47<T> = T extends [infer A extends number, ...unknown[]] ? A : never;
type N1 = FirstNum47<[3, "two", true]>; // 3
type N2 = FirstNum47<["x", 1]>; // never
// Parsing literal numbers from template strings — keeps the literal narrow
type ToNumber<S extends string> = S extends `${infer N extends number}` ? N : never;
type Year = ToNumber<"2026">; // 2026 (literal, not just `number`)
Output: (none)
infer in template literal types
Template literal types support infer exactly the way generic type wrappers do — name any string slot to read it out.
// Parse a route like "/users/:id"
type RouteParam<S extends string> =
S extends `${string}:${infer P}/${infer Rest}`
? P | RouteParam<`/${Rest}`>
: S extends `${string}:${infer P}`
? P
: never;
type P = RouteParam<"/users/:userId/posts/:postId">;
// "userId" | "postId"
// Split a string on a delimiter
type Split<S extends string, D extends string> =
S extends `${infer Head}${D}${infer Tail}`
? [Head, ...Split<Tail, D>]
: [S];
type Parts = Split<"2026-05-25", "-">;
// ["2026", "05", "25"]
// Replace
type Replace<S extends string, From extends string, To extends string> =
S extends `${infer L}${From}${infer R}` ? `${L}${To}${R}` : S;
type R1 = Replace<"hello world", "world", "TS">;
// "hello TS"
Output: (none)
Distributive vs non-distributive
When the conditional's left-hand side is a bare generic parameter and T is a union, the conditional distributes — once over each union member, then results unioned back. To opt out, wrap both sides in a one-tuple [T] extends [U].
// Distributive — runs once per union member
type Boxed<T> = T extends string ? { kind: "s"; value: T } :
T extends number ? { kind: "n"; value: T } :
never;
type B = Boxed<string | number>;
// { kind: "s"; value: string } | { kind: "n"; value: number }
// Non-distributive
type IsExactlyString<T> = [T] extends [string] ? true : false;
type Y1 = IsExactlyString<"hello">; // true — literal extends string
type Y2 = IsExactlyString<string | number>; // false — not all members are string
Output: (none)
infer works with both forms; the distribution rule applies to the conditional itself, not to the infer slots.
Custom DeepReadonly with infer
Combining recursion, mapped types, and infer over the array branch yields a real DeepReadonly that handles plain objects, arrays, tuples, and maps without freezing functions or primitives.
type DeepReadonly<T> =
T extends (...args: any[]) => any ? T :
T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepReadonly<U>> :
T extends Map<infer K, infer V> ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>> :
T extends Set<infer U> ? ReadonlySet<DeepReadonly<U>> :
T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } :
T;
interface AppConfig {
server: { host: string; ports: number[] };
db: Map<string, { url: string }>;
}
type Frozen = DeepReadonly<AppConfig>;
// {
// readonly server: { readonly host: string; readonly ports: ReadonlyArray<number> };
// readonly db: ReadonlyMap<string, { readonly url: string }>;
// }
Output: (none)
Promisify with infer
Wrap every property type of an object in a Promise, leaving function-typed properties alone except to promisify their return. A real "promisify" with infer on both the function signature and its return.
type Promisify<T> = {
[K in keyof T]: T[K] extends (...args: infer A) => infer R
? (...args: A) => Promise<Awaited<R>>
: Promise<T[K]>;
};
interface SyncApi {
count: number;
add(a: number, b: number): number;
who(): string;
}
type AsyncApi = Promisify<SyncApi>;
// {
// count: Promise<number>;
// add: (a: number, b: number) => Promise<number>;
// who: () => Promise<string>;
// }
Output: (none)
Common pitfalls
- Using
inferoutsideextends—inferis only legal in a conditional type's extends clause. Anywhere else (a generic parameter, an interface, a mapped type body) is a syntax error. - Forgetting to default to
never— A non-matching false branch should usually benever, notunknownor the input. Returning the input pollutes unions:T extends Promise<infer U> ? U : TmakesAwaited<number>resolve tonumber, which is what you want — butT extends Promise<infer U> ? U : neverwould erase non-promises entirely. Choose based on intent. - Distribution surprises —
T extends Foo<infer X> ? X : neverdistributes over a unionT, producing a union ofXs. Wrap with[T] extends [Foo<infer X>]to stop distribution if you wanted the union as a whole. anypoisons inference — Inferring insideanyreturnsany. Constrain withunknownor a precise pattern.- Excessive depth errors — Recursive conditional types hit the compiler's recursion limit (currently around 50–100). Reformulate or use an accumulator tuple to lower the depth.
infer X extends Yrequires TS 4.7+ — The constrained form is a relatively recent addition. Confirm yourtsconfigtarget/liband TS version before relying on it.- Optional vs
undefinedin tuple inference —[infer A, ...infer R]requires at least one element. For possibly-empty tuples, pattern-match both[]and[H, ...T]cases. - Inferring
this—thisis a parameter slot for the type checker:T extends (this: infer U, ...args: any) => any ? U : never. Forgetting thethis:form gives you the first real argument instead. - Inferring from index signatures —
T extends Record<string, infer V>matches any object, because every object structurally satisfies that pattern. To target intentional records, constrainTfirst. - Naming
infer Xafter a generic parameter doesn't reuse it —type Foo<X> = T extends infer X ? ... : ...shadows the outerXwith a fresh, unrelated type variable. Rename the inner one if you actually want both visible.
Real-world recipes
Recipe 1 — Typing the result of a Zod-style parser
You want to know what parse(schema) returns without re-declaring the shape. Use infer over the schema's parse method.
interface Parser<T> {
parse(input: unknown): T;
}
type ParsedOf<S> = S extends Parser<infer T> ? T : never;
const userSchema = {
parse(input: unknown): { id: number; name: string } {
return input as { id: number; name: string };
},
};
type User = ParsedOf<typeof userSchema>;
// { id: number; name: string }
const u: User = userSchema.parse({ id: 1, name: "Alice" });
console.log(u.name);
Output:
Alice
Recipe 2 — Extract the args of a generic API client
When a method's signature is hidden behind a generic, infer gives you each piece for use elsewhere (e.g. building a wrapper that retries or logs).
class ApiClient {
async get<T>(path: string): Promise<T> {
return fetch(path).then((r) => r.json()) as Promise<T>;
}
async post<T>(path: string, body: unknown): Promise<T> {
return fetch(path, { method: "POST", body: JSON.stringify(body) })
.then((r) => r.json()) as Promise<T>;
}
}
type MethodOf<C, K extends keyof C> = C[K];
type ResultOf<M> = M extends (...args: any[]) => Promise<infer R> ? R : never;
type GetResult = ResultOf<MethodOf<ApiClient, "get">>; // unknown (generic erased)
type PostResult = ResultOf<MethodOf<ApiClient, "post">>; // unknown
// Use directly with the call site to keep the T narrow
async function withRetry<R>(fn: () => Promise<R>, n = 3): Promise<R> {
let lastErr: unknown;
for (let i = 0; i < n; i++) {
try { return await fn(); } catch (e) { lastErr = e; }
}
throw lastErr;
}
const api = new ApiClient();
const user = await withRetry(() => api.get<{ name: string }>("/me"));
console.log(user.name);
Output:
Alice
Recipe 3 — Parse route params at the type level
Type-check a router's handler against the :param segments in its path, with no runtime overhead. infer walks the template literal.
type RouteParams<S extends string> =
S extends `${string}:${infer Param}/${infer Rest}`
? { [K in Param | keyof RouteParams<`/${Rest}`>]: string }
: S extends `${string}:${infer Param}`
? { [K in Param]: string }
: Record<string, never>;
type Handler<S extends string> = (req: { params: RouteParams<S> }) => unknown;
const userPostHandler: Handler<"/users/:userId/posts/:postId"> = (req) => {
return `${req.params.userId}/${req.params.postId}`;
};
console.log(userPostHandler({ params: { userId: "alice", postId: "42" } }));
Output:
alice/42
Recipe 4 — Build a type-safe currying helper
Currying takes a multi-arg function and turns it into a chain of single-arg functions. With infer you can express the chain at the type level.
type Curry<F> =
F extends (a: infer A, ...rest: infer R) => infer Ret
? R extends []
? (a: A) => Ret
: (a: A) => Curry<(...rest: R) => Ret>
: never;
function curry<F extends (...args: any[]) => any>(fn: F): Curry<F> {
const c = (acc: unknown[]) =>
(x: unknown) => {
const all = [...acc, x];
return all.length >= fn.length ? fn(...all) : c(all);
};
return c([]) as Curry<F>;
}
const add3 = (a: number, b: number, c: number): number => a + b + c;
const curried = curry(add3);
console.log(curried(1)(2)(3));
Output:
6
Recipe 5 — Unwrap nested promises in a generic safeRun
safeRun returns [err, null] | [null, value] for any function, sync or async. infer recovers the success type even through nested promises.
type Result<T> = [Error, null] | [null, T];
type Resolved<F> = F extends (...args: any[]) => infer R
? Awaited<R>
: never;
async function safeRun<F extends (...args: any[]) => any>(
fn: F,
...args: Parameters<F>
): Promise<Result<Resolved<F>>> {
try {
const value = await fn(...args);
return [null, value as Resolved<F>];
} catch (e) {
return [e instanceof Error ? e : new Error(String(e)), null];
}
}
async function fetchScore(): Promise<Promise<number>> {
return Promise.resolve(42);
}
const [err, score] = await safeRun(fetchScore);
// ^ score: number | null
if (!err) console.log("score:", score);
Output:
score: 42
Recipe 6 — Discriminator type from a class hierarchy
Walk a class's static members with infer to pull out the discriminant string from a kind literal field.
abstract class Shape {
abstract readonly kind: string;
}
class Circle extends Shape {
readonly kind = "circle" as const;
constructor(public radius: number) { super(); }
}
class Square extends Shape {
readonly kind = "square" as const;
constructor(public side: number) { super(); }
}
type KindOf<C> = C extends new (...args: any[]) => { kind: infer K } ? K : never;
type Kinds = KindOf<typeof Circle | typeof Square>;
// "circle" | "square"
function describe(s: Circle | Square): string {
return s.kind === "circle" ? `circle r=${s.radius}` : `square s=${s.side}`;
}
console.log(describe(new Circle(5)));
console.log(describe(new Square(3)));
Output:
circle r=5
square s=3
Recipe 7 — Pulling a query's argument types from an SQL-ish template
Use infer over template literal types to detect ? placeholders in a SQL string and produce a tuple of unknown (or any other type) the same length.
type Placeholders<S extends string, Acc extends unknown[] = []> =
S extends `${string}?${infer Rest}`
? Placeholders<Rest, [...Acc, unknown]>
: Acc;
function query<S extends string>(sql: S, ...args: Placeholders<S>): void {
console.log("running:", sql, "with", args);
}
query("SELECT * FROM users WHERE id = ? AND name = ?", 1, "Alice");
// query("SELECT * FROM users WHERE id = ?", 1, "Alice"); // Error — too many args
Output:
running: SELECT * FROM users WHERE id = ? AND name = ? with [ 1, 'Alice' ]
Recipe 8 — Combining infer X extends number to validate config keys
Parse a config key like "timeout-30s" and statically extract the numeric portion as a literal type.
type ExtractSeconds<S extends string> =
S extends `timeout-${infer N extends number}s` ? N : never;
type T1 = ExtractSeconds<"timeout-30s">; // 30
type T2 = ExtractSeconds<"timeout-5s">; // 5
type T3 = ExtractSeconds<"hold-on">; // never
function setTimeoutKey<K extends string>(key: K, _value: ExtractSeconds<K>): void {
console.log("set", key);
}
setTimeoutKey("timeout-30s", 30);
// setTimeoutKey("timeout-30s", 31); // Error — value must literally be 30
// setTimeoutKey("hold-on", 1); // Error — never
Output:
set timeout-30s