Understanding TypeScript’s infer Keyword with Real Examples

TypeScript infer — Your Tiny Spy Inside Types

Imagine you’ve got a box with something inside, but there’s no label on it.
You can open it, peek at what’s inside, give it a name, and then use that name later.

That’s exactly what infer does in TypeScript — except our “boxes” are types.

infer is used only inside conditional types.
It’s a way of telling TypeScript:

“Find out what type is here, give it a name, and let me use it.”


Syntax

T extends SomeType<infer U> ? X : Y
  • T — the type we’re checking.
  • SomeType — the pattern where we want to grab a type U.
  • If T matches that pattern, then in the ? X branch we can use U.
  • If not — the : Y branch runs.

Why would you use infer?

You don’t always know a type in advance.
Sometimes it’s buried deep inside an array, function, or promise.

infer lets you automatically extract that inner type, so you can:

  • Avoid writing long generic type parameters like <T, R, A, Z>;
  • Prevent duplicating type definitions when code changes;
  • Keep type safety and IntelliSense.

Real-World Scenarios

1. Extracting a type from complex structures

type UnwrapPromise<T> = T extends Promise<infer R> ? R : T;
type UnwrapArray<T> = T extends (infer U)[] ? U : T;

type Data = Promise<{ users: string[] }>;
type Result = UnwrapArray<UnwrapPromise<Data>["users"]>; 
// string

Useful for: working with API responses where the result is Promise → object → array.


2. Type-safe wrappers

function withLogging<T extends (...args: any[]) => any>(fn: T) {
  return (...args: Parameters<T>): ReturnType<T> => {
    console.log("Calling", fn.name, args);
    return fn(...args);
  };
}

Both Parameters and ReturnType under the hood use infer to pull out arguments and return types.
Useful for: middleware, logging, decorators.


3. Inferring from configuration objects

const routes = {
  home: "/",
  profile: "/user/:id",
  settings: "/settings",
} as const;

type RouteKeys<T> = T extends Record<infer K, any> ? K : never;
type AppRoutes = RouteKeys<typeof routes>; 
// "home" | "profile" | "settings"

Useful for: routing, enum-like structures.


4. Making your own utility types

type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;

type A = First<[string, number, boolean]>; // string

Useful for: tuple handling, argument parsing.


Quick Reference — When to Use infer

SituationPattern Example
Get array element type(infer U)[]
Get function return type(...args: any[]) => infer R
Get object keysRecord<infer K, any>
Get first tuple element[infer F, ...any[]]

How to “teach” infer to find what you want

infer doesn’t magically “search” — you describe the shape of the type in extends, and TS checks if the input matches.

Algorithm:

  1. Imagine the general shape of the type you want.
  2. Put infer X where you want to capture part of it.
  3. Make the rest of the pattern flexible (any, ...any[]).
  4. Use ? : to decide what to return if the match works or fails.

Step-by-Step: Designing extends for infer


Task 1: Extract a function’s return type

Scenario: We have a function, but we want its result type without manually writing it.

Steps:

  1. Functions in TS look like (...args: any[]) => Something.
  2. Put infer R in place of Something.
  3. If T fits “any function”, return R, else never.
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type A = MyReturnType<() => number>; // number
type B = MyReturnType<(x: string) => Promise<boolean>>; // Promise<boolean>

Takeaway: We taught TS to find the return type just by describing the function shape.


Task 2: Extract an array’s element type

Scenario: API returns User[], but we want User.

Steps:

  1. Arrays are SomeType[].
  2. Put infer U in place of SomeType.
  3. If T is an array, return U, else never.
type ElementType<T> = T extends (infer U)[] ? U : never;

type A = ElementType<string[]>;  // string
type B = ElementType<number[]>;  // number
type C = ElementType<boolean>;   // never

Takeaway: infer grabs the inner type without extra generics.

Task 3: Get the first element of a tuple

Scenario: We have [string, number, boolean] and want string.

Steps:

  1. A tuple is a fixed-length array: [Type1, Type2, ...].
  2. Put infer F in the first position; rest as ...any[].
  3. If T fits, return F, else never.
type First<T> = T extends [infer F, ...any[]] ? F : never;

type A = First<[string, number, boolean]>; // string
type B = First<[boolean]>;                 // boolean
type C = First<[]>;                         // never

Takeaway: infer easily parses tuple positions.


General Blueprint for infer Patterns

  1. Picture the type’s shape.
  2. Put infer where you want the piece.
  3. Keep the rest broad enough so it still matches.
  4. Use conditional branches to control the result.

With practice, this becomes like LEGO: you see a type and instantly know the extends + infer shape to grab what you need.

Leave a Reply

Your email address will not be published. Required fields are marked *