Skip to content
C Codeloom
TypeScript

TypeScript infer Keyword Explained

Master the infer keyword in TypeScript conditional types. Learn how to extract parts of complex types, build utility helpers, and write expressive generics.

·5 min read · By Codeloom
Intermediate 8 min read

What you'll learn

  • What the infer keyword does at the type level
  • How to extract return types, parameters, and array elements
  • Patterns for inferring tuple, promise, and function parts
  • How distribution interacts with inference
  • When inference is the wrong tool

Prerequisites

  • Basic TypeScript generics
  • Familiarity with conditional types

What and Why

The infer keyword introduces a fresh type variable inside the extends clause of a conditional type. It tells the compiler: “match this shape, and capture this slot as a name I can use on the true branch.” Without infer, conditional types can only ask yes-or-no questions. With it, they can pull pieces out of a structure.

This is the engine behind almost every advanced utility type in the standard library. ReturnType, Parameters, Awaited, InstanceType, and many community helpers all rely on infer. Understanding it unlocks the ability to write your own ergonomic, reusable type helpers instead of copy-pasting brittle signatures.

Mental Model

Think of infer as a pattern variable inside a structural template. You write the shape you expect, leave a hole with infer R, and TypeScript fills the hole by matching. If the shape does not match, the conditional falls to the false branch.

A useful analogy is destructuring at the type level. Just as const { name } = user pulls name out of an object value, T extends { name: infer N } ? N : never pulls name out of an object type.

Hands-on Example

Let us build a few common helpers from scratch. We start with ReturnType, which extracts the return type of a function.

type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type A = MyReturnType<() => number>;        // number
type B = MyReturnType<(s: string) => User>; // User
type C = MyReturnType<string>;              // never

The pattern (...args: any[]) => infer R matches any callable. When the match succeeds, R is bound to the return type. When it fails, the conditional returns never.

We can extract parameters as a tuple too.

type MyParameters<T> = T extends (...args: infer P) => any ? P : never;
type Args = MyParameters<(id: string, n: number) => void>; // [string, number]

The same idea applies to arrays and promises.

type ElementOf<T> = T extends (infer E)[] ? E : never;
type Unwrap<T> = T extends Promise<infer V> ? V : T;
type Extract<T> = T extends Promise<infer V> ? V : T;

    Input:  Promise<User>
              |
              v
    Pattern: Promise<  infer V  >
                          ^
                          |
                     binds V = User
                          |
                          v
    Result:  User
How infer binds a slot during pattern matching

You can place multiple infer variables in the same conditional. For example, splitting a tuple into head and tail.

type Head<T> = T extends [infer H, ...any[]] ? H : never;
type Tail<T> = T extends [any, ...infer R] ? R : never;

Common Pitfalls

The first surprise is distribution. A naked type parameter in extends distributes over unions. If you do not want that, wrap the parameter in a tuple: [T] extends [...] ? ....

type Bad<T> = T extends (infer U)[] ? U : never;
type X = Bad<number[] | string[]>; // number | string, distributed

The second pitfall is over-broad patterns. Using any[] for parameters is convenient but discards higher-order context. Prefer unknown[] when you only care about arity, and use explicit parameter types when you need them.

A third trap is forgetting that infer only works inside the extends clause of a conditional type. You cannot use it in the true branch, in a mapped type key, or at the top level of an alias.

Finally, when the same infer name appears in covariant positions multiple times, TypeScript produces a union. In contravariant positions, you get an intersection. This is rarely what beginners expect and can lead to confusing inferred types.

Best Practices

Name your infer variables clearly. R for return, P for parameters, E for element, K for key. Consistent naming makes complex helpers readable.

Keep helpers small and composable. A chain of two simple helpers is usually clearer than one giant conditional. If you find yourself nesting three levels of infer, consider whether a runtime helper would serve users better.

Always provide a default branch. Returning never on no-match is conventional and lets the type system catch misuse at the call site.

Test your helpers with explicit assertions using a small Expect and Equal pair. Type-level tests catch regressions when the compiler upgrades inference heuristics.

Wrap-up

The infer keyword is the destructuring operator of the type system. It turns conditional types from yes-or-no checks into structural extractors, enabling the small library of helpers TypeScript ships with and the countless community ones built on top. Master the pattern of “shape with a hole” and you can write helpers that feel native.

Reach for infer when you need to read part of a type. Avoid it when a simple lookup T['field'] or a mapped type does the job. The right tool keeps your types honest and your editor fast.