Skip to main content

You need to extract the return type of a function. The element type of an array. The resolution type of a Promise. The type inside a generic wrapper. In any other language, you'd write a utility function. In TypeScript, you write a type — and the infer keyword does the rest.

infer is TypeScript's mind-reader. It lets you declare a temporary type variable inside a conditional type and then use that variable to construct a new type. It's the secret behind ReturnType, Parameters, Awaited, and every other type-extraction utility in the standard library.

This chapter unlocks the next level of TypeScript mastery. Once you understand infer, you can write types that analyze, extract, and transform other types — the foundation of type-level programming.

The Problem: Extracting Types From Types​

You have a function type. You want its return type:

type MyFunction = (name: string, age: number) => User;

// How do you get 'User' from 'MyFunction'?

You have a Promise type. You want the resolved type:

type MyPromise = Promise<User[]>;

// How do you get 'User[]' from 'MyPromise'?

You have an array type. You want the element type:

type MyArray = User[];

// How do you get 'User' from 'MyArray'?

This is the problem infer solves. It lets you "reach into" a type and pull out a piece of it.

Conditional Types: The Foundation​

Before infer, you need to understand conditional types. They're exactly what they sound like — types that make decisions:

type IsString<T> = T extends string ? true : false;

type A = IsString<"hello">; // true
type B = IsString<42>; // false
type C = IsString<string>; // true
type D = IsString<string | number>; // boolean (distributes!)

The syntax is T extends U ? X : Y. If T is assignable to U, the type is X. Otherwise, it's Y.

We'll cover conditional types in depth in Chapter 14. For now, the key insight is: conditional types create a "type scope" where TypeScript can reason about relationships between types. And infer lets you capture pieces of those types.

infer: The Type Extractor​

infer declares a type variable that TypeScript will fill in:

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

Let's break this down:

  1. T extends (...args: unknown[]) => infer R — Does T look like a function? If so, capture its return type as R.
  2. ? R — If yes, the result is R (the captured return type).
  3. : never — If no, the result is never (T is not a function).

When you use it:

type MyReturnType = ReturnType<(name: string) => User>;
// = User

TypeScript sees that (name: string) => User matches (...args: unknown[]) => infer R, captures User as R, and returns R.

infer in Different Positions​

You can use infer anywhere in a type pattern:

// Extract the element type of an array
type ElementType<T> = T extends (infer E)[] ? E : never;
type E1 = ElementType<string[]>; // string
type E2 = ElementType<User[]>; // User

// Extract the first argument of a function
type FirstArgument<T> = T extends (first: infer F, ...rest: unknown[]) => unknown ? F : never;
type F1 = FirstArgument<(name: string, age: number) => User>; // string

// Extract the resolved type of a Promise
type Awaited<T> = T extends Promise<infer R> ? R : T;
type A1 = Awaited<Promise<User>>; // User
type A2 = Awaited<Promise<Promise<User>>>; // Promise<User> — only one level!

Recursive infer for Deep Unwrapping​

The standard Awaited only unwraps one level. For deeply nested Promises, use recursion:

type DeepAwaited<T> = T extends Promise<infer R> ? DeepAwaited<R> : T;

type D1 = DeepAwaited<Promise<Promise<Promise<User>>>>; // User

Recursive conditional types are one of TypeScript's most powerful features. They let you define type operations that "loop" until they reach a base case.

Real-World infer Patterns​

Pattern 1: Extracting Action Payloads​

type Action =
| { type: "ADD_TODO"; payload: { text: string } }
| { type: "TOGGLE_TODO"; payload: { id: number } }
| { type: "DELETE_TODO"; payload: { id: number } };

type PayloadByType<A, T extends A["type"]> = A extends { type: T; payload: infer P } ? P : never;

type AddTodoPayload = PayloadByType<Action, "ADD_TODO">; // { text: string }
type ToggleTodoPayload = PayloadByType<Action, "TOGGLE_TODO">; // { id: number }

Pattern 2: Extracting Component Props (React)​

type ComponentProps<T> = T extends React.ComponentType<infer P> ? P : never;

type ButtonProps = ComponentProps<typeof Button>;
// { onClick: () => void; label: string; disabled?: boolean; }

Pattern 3: Extracting the Success Type from a Result​

type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };

type SuccessType<R> = R extends { success: true; data: infer D } ? D : never;

type UserResult = Result<User>;
type UserData = SuccessType<UserResult>; // User

Pattern 4: Flattening Nested Arrays​

type Flatten<T> = T extends (infer U)[] ? Flatten<U> : T;

type F1 = Flatten<number[][][]>; // number
type F2 = Flatten<string[]>; // string
type F3 = Flatten<number>; // number (not an array — returns itself)

Pattern 5: Extracting the Value Type from a Dictionary​

type Dictionary<T> = { [key: string]: T };

type ValueType<D> = D extends Dictionary<infer V> ? V : never;

type UserDict = Dictionary<User>;
type UserValue = ValueType<UserDict>; // User

Multiple infer Declarations​

You can use infer multiple times in a single conditional type:

type FunctionSignature<T> = T extends (...args: infer P) => infer R ? { params: P; return: R } : never;

type Sig = FunctionSignature<(name: string, age: number) => User>;
// { params: [name: string, age: number]; return: User }

This extracts both the parameters and the return type in one operation.

infer with Template Literal Types​

infer works inside template literal types too (which we'll cover fully in Chapter 12):

type ExtractPrefix<T> = T extends `prefix_${infer Rest}` ? Rest : never;

type A = ExtractPrefix<"prefix_user">; // "user"
type B = ExtractPrefix<"prefix_product">; // "product"
type C = ExtractPrefix<"other">; // never

infer with Variadic Tuples​

You can use infer to extract parts of tuples:

type First<T> = T extends [infer F, ...unknown[]] ? F : never;
type Last<T> = T extends [...unknown[], infer L] ? L : never;
type Tail<T> = T extends [unknown, ...infer Rest] ? Rest : never;

type F = First<[string, number, boolean]>; // string
type L = Last<[string, number, boolean]>; // boolean
type T = Tail<[string, number, boolean]>; // [number, boolean]

The Limits of infer​

infer is powerful but not magic. It can only extract types from positions that TypeScript can structurally match:

// This works — the structure is clear
type Unwrap<T> = T extends Promise<infer R> ? R : T;

// This doesn't work — TypeScript can't infer from a string pattern like this
type ExtractDomain<T> = T extends `${infer Protocol}://${infer Domain}/${infer Path}` ? Domain : never;
// Actually, this DOES work with template literal types! (Chapter 12)

The key limitation: infer works with structural type patterns. If TypeScript can match the structure, it can extract from it.

Building Your Own Utility Types​

Now you can build utility types that rival the standard library:

// Extract the type of a specific property
type PropType<T, K extends keyof T> = T[K];

// Extract all function property names from a type
type FunctionKeys<T> = {
[K in keyof T]: T[K] extends (...args: unknown[]) => unknown ? K : never;
}[keyof T];

// Extract only the function properties from a type
type FunctionProperties<T> = Pick<T, FunctionKeys<T>>;

// Make all properties of a nested object optional (deep partial)
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;

// Extract the union of all values in an object
type Values<T> = T[keyof T];

Try This: The Infer Keyword​

  1. Write a MyReturnType<T> that extracts the return type of a function.
  2. Write a MyParameters<T> that extracts the parameter types as a tuple.
  3. Write a Flatten<T> that recursively flattens nested arrays.
  4. Write a PromiseType<T> that extracts the resolved type of a Promise (one level).
  5. Write a DeepPromiseType<T> that recursively unwraps nested Promises.
  6. Write a FirstArgument<T> that extracts the type of the first function argument.

Time needed: 25 minutes.

What to notice: How infer turns conditional types from simple "if-else" into powerful type-extraction tools. How recursive types let you process arbitrarily nested structures. How the standard utility types are just applications of these same patterns.

The Bridge​

You now know how to extract types from types. But there's another dimension to type manipulation: transforming types. Not just pulling pieces out, but building new types from old ones — adding properties, removing properties, making things optional, making things readonly.

That's mapped types. And they're the subject of Chapter 13.

But first, there's one more extraction pattern so surprising it deserves its own chapter: template literal types. String manipulation. At the type level. It sounds like a party trick, but it's one of TypeScript's most practical features.


In the next chapter: template literal types — how to manipulate strings at the type level, and why this unlocks type-safe routing, event names, and CSS-in-JS.