Skip to content
JL

Home

About

Blog

Contact

Shop

Portfolio

Privacy

TOS

Click to navigate

  1. Home
  2. Joshua R. Lehman's Blog
  3. The infer Keyword in TypeScript

Table of contents

  • Share on X
  • Discuss on X

Related Articles

keyof and typeof Operators in TypeScript
TypeScript
8m
Jul 19, 2026

keyof and typeof Operators in TypeScript

keyof extracts the union of property names from a type. typeof extracts the type of a value at compile time. Together they bridge the gap between your runtime values and your type-level code — letting you derive types from what already exists.

#keyof Operator#typeof Operator+5
Mapped Types: Transforming Types in TypeScript
TypeScript
10m
Jul 12, 2026

Mapped Types: Transforming Types in TypeScript

Mapped types are TypeScript's for-loop for types. They iterate over the keys of an existing type and produce a new type with transformed properties — the same way Array.map transforms every element in an array.

#Mapped Types#TypeScript Transformation+5
Default Generic Parameters in TypeScript
TypeScript
6m
Jun 21, 2026

Default Generic Parameters in TypeScript

Default generic parameters let you make type arguments optional by providing a fallback type when the caller doesn't specify one. They're the reason Result<User> works without writing Result<User, Error> every time — the Error default is already baked in.

#Default Generic Parameters#TypeScript Generics+4
Ask me anything! 💬

© Joshua R. Lehman

Full Stack Developer

Crafted with passion • Built with modern web technologies

2026 • All rights reserved

Contents

  • What Is the infer Keyword
  • Inferring Return Types
  • Inferring Parameter Types
  • Inferring Array Element Types
  • Inferring Promise Unwrapping
  • Real-World Usage
  • Best Practices
  • What Is Next
  • Key Takeaways
TypeScript

The infer Keyword in TypeScript

July 5, 2026•6 min read
Joshua R. Lehman
Joshua R. Lehman
Author
TypeScript infer keyword for extracting types in conditional type expressions
The infer Keyword in TypeScript

The infer keyword is TypeScript's way of capturing a type from within a conditional check. Think of it like a regex capture group — just as /(\d+)/ pulls a number out of a string match, infer R pulls a type out of a type match. It can only appear inside the extends clause of a conditional type, and it gives the captured type a name you can use in the true branch. In this post, you'll learn how infer works, how to use it to extract return types, parameter types, array element types, and Promise values, and how standard utility types like ReturnType and Parameters are built with it.

Key Constraint

infer can only appear on the right-hand side of an extends clause in a conditional type. It declares a type variable that TypeScript will fill in by pattern matching against the input type.

What Is the infer Keyword

Without infer, conditional types can only check whether a relationship holds — they can't extract the type involved in that relationship. The infer keyword adds extraction:

// Without infer — checks if T is a function, returns boolean-like types
type IsFunction<T> = T extends (...args: any[]) => any ? true : false;
 
// With infer — checks if T is a function AND captures what it returns
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

In the second form, if T is a function, TypeScript pattern-matches its return type and binds it to R. The R variable is then available in the true branch (R) and can be returned as the resolved type.

Inferring Return Types

The most common use of infer is extracting a function's return type. TypeScript's built-in ReturnType<T> does exactly this:

type ReturnType<T extends (...args: any) => any> = T extends (
  ...args: any
) => infer R
  ? R
  : any;
 
function getUser() {
  return { id: 1, name: "Alice", role: "admin" as const };
}
 
type User = ReturnType<typeof getUser>;
// { id: number; name: string; role: "admin" }

This is incredibly useful when you want to derive the return type of a function you don't control — from a third-party library, for instance — without importing a type that might not be exported.

// Library doesn't export this type, but you can derive it
import { createConnection } from "some-database-library";
type Connection = ReturnType<typeof createConnection>;

Inferring Parameter Types

You can infer the parameter types of a function using infer on the arguments side:

type Parameters<T extends (...args: any) => any> = T extends (
  ...args: infer P
) => any
  ? P
  : never;
 
function createPost(title: string, authorId: number, published: boolean) {
  // ...
}
 
type PostParams = Parameters<typeof createPost>;
// [title: string, authorId: number, published: boolean]
 
// Extract just the first parameter
type FirstParam<T extends (...args: any) => any> = T extends (
  first: infer F,
  ...rest: any[]
) => any
  ? F
  : never;
 
type TitleType = FirstParam<typeof createPost>; // string

Parameters Returns a Tuple

Parameters<T> returns a tuple type representing all the argument types in order. You can index into it with Parameters<T>[0] to get just the first parameter's type.

Inferring Array Element Types

infer works well for extracting what's inside container types:

type ElementType<T> = T extends (infer E)[] ? E : never;
 
type A = ElementType<string[]>; // string
type B = ElementType<number[][]>; // number[] — unwraps one level
type C = ElementType<boolean>; // never — not an array
 
// For readonly arrays too
type ReadonlyElementType<T> = T extends readonly (infer E)[] ? E : never;
 
type D = ReadonlyElementType<readonly string[]>; // string

You can make it recursive to unwrap nested arrays:

type DeepElementType<T> = T extends (infer E)[] ? DeepElementType<E> : T;
 
type E = DeepElementType<string[][][]>; // string

Inferring Promise Unwrapping

infer shines when working with async code. TypeScript's Awaited<T> utility type is built on this pattern:

type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
 
type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<Promise<number[]>>; // number[]
type C = UnwrapPromise<string>; // string — not a Promise, returned as-is
 
// Recursive version — handles Promise<Promise<...>>
type DeepAwaited<T> = T extends Promise<infer U> ? DeepAwaited<U> : T;
 
type D = DeepAwaited<Promise<Promise<string>>>; // string

TypeScript 4.5 introduced the built-in Awaited<T> utility type which handles more edge cases than a simple recursive unwrap. Prefer the built-in over rolling your own.

Real-World Usage

Here's a pattern for building a type-safe event system where event handler return types are extracted automatically:

type EventHandlers = {
  click: (x: number, y: number) => void;
  keydown: (key: string, modifiers: string[]) => boolean;
  resize: (width: number, height: number) => { handled: boolean };
};
 
// Extract return type for a specific event
type HandlerReturn<T extends keyof EventHandlers> = ReturnType<
  EventHandlers[T]
>;
 
type ClickReturn = HandlerReturn<"click">; // void
type KeyReturn = HandlerReturn<"keydown">; // boolean
type ResizeReturn = HandlerReturn<"resize">; // { handled: boolean }
 
// Extract first parameter type for a specific event
type FirstArg<T extends (...args: any[]) => any> = T extends (
  first: infer F,
  ...rest: any[]
) => any
  ? F
  : never;
 
type ClickX = FirstArg<EventHandlers["click"]>; // number
type KeyName = FirstArg<EventHandlers["keydown"]>; // string

Another common pattern extracts the type inside a custom wrapper:

type Boxed<T> = { value: T; tag: string };
 
// Unbox a Boxed<T> to get T
type Unbox<T> = T extends Boxed<infer U> ? U : T;
 
type NumberBox = Boxed<number>;
type Unboxed = Unbox<NumberBox>; // number

Build Your Own Utility Types

Once you understand infer, every TypeScript utility type becomes readable. Open node_modules/typescript/lib/lib.es5.d.ts and look at how ReturnType, Parameters, InstanceType, and ConstructorParameters are all built with the same pattern.

Best Practices

Name your inferred variables descriptively. infer R is fine in a short type, but infer ReturnValue or infer ElementType makes complex types much easier to read.

Handle the false branch intentionally. Returning never from the false branch is common, but sometimes you want to return T unchanged (passthrough) or a specific fallback type.

Don't over-infer. If TypeScript already exposes the type you need (like a library's exported type), use that directly rather than inferring it. Save infer for when types aren't directly accessible.

Multiple infer positions work. You can have multiple infer declarations in one extends clause:

type SplitFunction<T> = T extends (...args: infer Args) => infer Return
  ? { args: Args; return: Return }
  : never;
 
type Info = SplitFunction<(x: number, y: string) => boolean>;
// { args: [x: number, y: string]; return: boolean }

Anti-Pattern

Don't use infer outside of conditional types — it's only valid inside the extends clause. Trying to use it elsewhere is a syntax error.

What Is Next

With conditional types and infer in your toolkit, you're ready for mapped types — TypeScript's way of transforming every property in a type according to a rule. Mapped types are how Partial<T>, Required<T>, and Readonly<T> work, and they pair beautifully with conditional types.

Key Takeaways

  • infer only works inside the extends clause of a conditional type — it declares a type variable TypeScript fills by pattern matching
  • ReturnType<T> and Parameters<T> are built-in utilities that use infer under the hood
  • You can infer from function arguments, return positions, array element positions, and generic type arguments
  • Multiple infer declarations in one clause are allowed and useful
  • Name inferred type variables descriptively to keep complex conditional types readable

You now understand the engine behind TypeScript's most powerful utility types. infer is the bridge between structural type checking and type extraction — and it unlocks a whole category of patterns you couldn't write any other way.