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
Tmatches that pattern, then in the? Xbranch we can useU. - If not — the
: Ybranch 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
| Situation | Pattern Example |
|---|---|
| Get array element type | (infer U)[] |
| Get function return type | (...args: any[]) => infer R |
| Get object keys | Record<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:
- Imagine the general shape of the type you want.
- Put
infer Xwhere you want to capture part of it. - Make the rest of the pattern flexible (
any,...any[]). - 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:
- Functions in TS look like
(...args: any[]) => Something. - Put
infer Rin place ofSomething. - If
Tfits “any function”, returnR, elsenever.
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:
- Arrays are
SomeType[]. - Put
infer Uin place ofSomeType. - If
Tis an array, returnU, elsenever.
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:
- A tuple is a fixed-length array:
[Type1, Type2, ...]. - Put
infer Fin the first position; rest as...any[]. - If
Tfits, returnF, elsenever.
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
- Picture the type’s shape.
- Put
inferwhere you want the piece. - Keep the rest broad enough so it still matches.
- 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.
