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 useU
. - 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
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 X
where 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 R
in place ofSomething
. - If
T
fits “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 U
in place ofSomeType
. - If
T
is 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 F
in the first position; rest as...any[]
. - If
T
fits, 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
infer
where 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.