TypeScript infer keyword — extract types from other types
← Back
April 4, 2026TypeScript8 min read

TypeScript infer keyword — extract types from other types

Published April 4, 20268 min read

The infer keyword is TypeScript's pattern-matching tool for types. It says "if this type matches this shape, extract this part of it and give me a name to use." It unlocks a whole class of utility types — extracting the return type of a function, the resolved type of a Promise, the element type of an array — without needing to manually specify the type. Once you understand it, you start seeing uses everywhere.

The core idea

infer appears inside conditional types. It creates a type variable that is bound if the condition matches:

typescript
// Without infer — you have to specify T manually
type Unwrap<T> = T extends Promise<unknown> ? unknown : T;

// With infer — TypeScript figures out T automatically
type Unwrap<T> = T extends Promise<infer T> ? T : never;
// "If T is a Promise of some type, infer what that type is and call it T"

type A = Unwrap<Promise<string>>;  // string
type B = Unwrap<Promise<number[]>>;  // number[]
type C = Unwrap<string>;            // never (not a Promise)

ReturnType — built with infer

TypeScript's built-in ReturnType is implemented with infer:

typescript
// How ReturnType is implemented
type ReturnType<T extends (...args: any) => any> =
  T extends (...args: any) => infer R ? R : never;
// "If T is a function that returns something, infer the return type as R"

// Usage
function getUser() {
  return { id: '1', name: 'Alice', role: 'admin' as const };
}

type User = ReturnType<typeof getUser>;
// { id: string; name: string; role: 'admin' }

// Async functions return Promises
async function fetchUser() {
  return { id: '1', name: 'Alice' };
}

type AsyncUser = ReturnType<typeof fetchUser>;  // Promise<{ id: string; name: string }>
type ResolvedUser = Awaited<ReturnType<typeof fetchUser>>;  // { id: string; name: string }

Extracting parameter types

typescript
// Extract ALL parameters as a tuple
type Parameters<T extends (...args: any) => any> =
  T extends (...args: infer P) => any ? P : never;

// Extract the first parameter
type FirstParam<T extends (...args: any) => any> =
  T extends (first: infer F, ...rest: any[]) => any ? F : never;

// Extract the last parameter
type LastParam<T extends (...args: any) => any> =
  T extends (...args: [...infer _, infer Last]) => any ? Last : never;

function handleEvent(type: string, payload: { data: unknown }, timestamp: number): void {}

type AllParams = Parameters<typeof handleEvent>;  // [string, { data: unknown }, number]
type First = FirstParam<typeof handleEvent>;       // string
type Last = LastParam<typeof handleEvent>;         // number

Unwrapping nested types

typescript
// Deep Promise unwrapping (handles Promise<Promise<T>>)
type DeepAwaited<T> =
  T extends Promise<infer Inner>
    ? DeepAwaited<Inner>
    : T;

type A = DeepAwaited<Promise<Promise<string>>>;  // string

// Array element type
type ElementOf<T> = T extends Array<infer E> ? E : never;

type StrEl = ElementOf<string[]>;   // string
type NumEl = ElementOf<number[]>;   // number

// Readonly array
type ReadonlyElement<T> = T extends ReadonlyArray<infer E> ? E : never;

Inferring from object property types

typescript
// Extract the type of a specific property
type PropType<T, K extends keyof T> = T extends { [P in K]: infer V } ? V : never;

interface User {
  id: string;
  role: 'admin' | 'user';
  metadata: { createdAt: Date };
}

type UserId = PropType<User, 'id'>;            // string
type UserRole = PropType<User, 'role'>;        // 'admin' | 'user'
type UserMeta = PropType<User, 'metadata'>;    // { createdAt: Date }

// Extract nested property type
type NestedProp<T, K1 extends keyof T, K2 extends keyof T[K1]> =
  T[K1] extends { [P in K2]: infer V } ? V : never;

type CreatedAt = NestedProp<User, 'metadata', 'createdAt'>;  // Date

Practical use case: typed event handlers

typescript
// Extract the payload type for a specific event
type EventMap = {
  'user.created': { userId: string; email: string };
  'order.placed': { orderId: string; total: number };
  'payment.failed': { orderId: string; error: string };
};

type EventPayload<T extends keyof EventMap> =
  EventMap[T] extends infer P ? P : never;

// Or more directly:
type EventPayload<T extends keyof EventMap> = EventMap[T];

function on<E extends keyof EventMap>(
  event: E,
  handler: (payload: EventPayload<E>) => void
): void {
  // register handler
}

// Handler is automatically typed based on the event name
on('user.created', (payload) => {
  console.log(payload.userId);  // string — TypeScript knows this
  console.log(payload.total);   // Error! 'total' doesn't exist on user.created payload
});

Multiple infer bindings

typescript
// Infer both key and value types from a Map
type MapTypes<T> =
  T extends Map<infer K, infer V>
    ? { key: K; value: V }
    : never;

type M = MapTypes<Map<string, number[]>>;
// { key: string; value: number[] }

// Infer from function overloads — gets the last overload
type LastOverloadReturn<T> =
  T extends { (...args: any): infer R; (...args: any): any } ? R :
  T extends (...args: any) => infer R ? R : never;

infer is the building block of all the TypeScript utility types you use daily. Once you can read conditional types with infer, the TypeScript type system stops feeling like magic and starts feeling like a tool you understand completely. That shift changes how you design type-safe libraries and APIs.

Share this
← All Posts8 min read