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.

bash
npx tsc --version

Output:

code
Version 5.5.4

Syntax

typescript
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

RuleDetail
Only inside extendsinfer X must appear in the extends clause of a conditional type
Fresh per useEvery infer X introduces a new variable, even with the same name
Multiple inferencesA single conditional can have many infer slots
Distributive over unionsGeneric T extends ... distributes by default; wrap [T] extends [...] to opt out
RecursiveA conditional may reference itself in the true branch (recursive conditional types, TS 4.1+)
infer X extends YTS 4.7+ — constrain the inferred type to a bound
Fallback to neverWhen 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.

typescript
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.

typescript
// 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.

typescript
// 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.

typescript
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.

typescript
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."

typescript
// 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.

typescript
// 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.

typescript
// 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].

typescript
// 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.

typescript
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.

typescript
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

  1. Using infer outside extendsinfer is only legal in a conditional type's extends clause. Anywhere else (a generic parameter, an interface, a mapped type body) is a syntax error.
  2. Forgetting to default to never — A non-matching false branch should usually be never, not unknown or the input. Returning the input pollutes unions: T extends Promise<infer U> ? U : T makes Awaited<number> resolve to number, which is what you want — but T extends Promise<infer U> ? U : never would erase non-promises entirely. Choose based on intent.
  3. Distribution surprisesT extends Foo<infer X> ? X : never distributes over a union T, producing a union of Xs. Wrap with [T] extends [Foo<infer X>] to stop distribution if you wanted the union as a whole.
  4. any poisons inference — Inferring inside any returns any. Constrain with unknown or a precise pattern.
  5. 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.
  6. infer X extends Y requires TS 4.7+ — The constrained form is a relatively recent addition. Confirm your tsconfig target/lib and TS version before relying on it.
  7. Optional vs undefined in tuple inference[infer A, ...infer R] requires at least one element. For possibly-empty tuples, pattern-match both [] and [H, ...T] cases.
  8. Inferring thisthis is a parameter slot for the type checker: T extends (this: infer U, ...args: any) => any ? U : never. Forgetting the this: form gives you the first real argument instead.
  9. Inferring from index signaturesT extends Record<string, infer V> matches any object, because every object structurally satisfies that pattern. To target intentional records, constrain T first.
  10. Naming infer X after a generic parameter doesn't reuse ittype Foo<X> = T extends infer X ? ... : ... shadows the outer X with 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.

typescript
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:

code
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).

typescript
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:

code
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.

typescript
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:

code
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.

typescript
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:

code
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.

typescript
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:

makefile
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.

typescript
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:

ini
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.

typescript
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:

sql
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.

typescript
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:

arduino
set timeout-30s