Skip to content
advanced Phase 11 · TypeScript Advanced Patterns

Advanced Inference

Use infer keyword, conditional inference, and recursive types.

1h
0 problems
Topic Progress 0%

Capture Types with infer

Capture Types with infer

infer may only appear inside an extends clause, and it binds whatever the compiler matched in that slot for reuse in the true branch. The standard moves:

type El<T> = T extends readonly (infer E)[] ? E : never;
type First<F> = F extends (a: infer A, ...r: any[]) => any ? A : never;
type Awaited2<T> = T extends Promise<infer U> ? Awaited2<U> : T;

Inference also runs at call sites: passing [1, 2] to function f<T>(x: T) infers number[], while an as const assertion (or a const type parameter in TS 5) shifts the result to the readonly tuple readonly [1, 2].

Inference Across Unions

Inference Across Unions

Conditional inference distributes like any other conditional: T extends Promise<infer U> over Promise<A> | Promise<B> yields A | B. When the shapes differ — Promise<X> | X — you get the union of every branch match, so wrap to check the whole thing:

type Unpromised<T> = [T] extends [Promise<infer U>] ? Unpromised<U> : T;
type KV<T> = T extends Map<infer K, infer V> ? [K, V] : never;

Two independent infer slots capture two positions at once (Map<K, V> above). Inference is directional: return types flow from the implementation, while parameters flow from the arguments you pass.

Recursive Types and Limits

Recursive Types and Limits

Recursive types model trees and self-similar data:

type Json =
  | string | number | boolean | null
  | Json[]
  | { [k: string]: Json };
type Flatten<T> = T extends Array<infer U> ? Flatten<U> | U : T;

Since TS 4.5, tail-recursive conditional types are optimized up to 1000 instantiations, so Flatten handles deep arrays; other recursion hits Type instantiation is excessively deep. Interview trap: Awaited unwraps nested promises — explain why the naive T extends Promise<infer U> ? U : T stops one level early on Promise<Promise<number>>.

Quiz

1. What does T extends Promise<infer U> ? U : T produce for Promise<Promise<number>>?

Question 1 options

2. Where is the infer keyword allowed?

Question 2 options

3. Which assertion infers a readonly tuple of literals instead of number[]?

Question 3 options

Flashcards

Question

infer keyword

Answer

Placeholder inside an extends clause that captures the matched type for the true branch.

Question

Distributed inference

Answer

Union input + naked parameter = infer runs per member and unions the results.

Question

Tuple wrapper trick

Answer

[T] extends [X] checks the union as one type, stopping per-member distribution.

Question

Recursive conditional types

Answer

Tail-recursive forms are optimized up to 1000 steps (TS 4.5+); deeper hits an instantiation error.

Question

as const vs const type param

Answer

Both infer literal/readonly tuples; the const modifier (TS 5) does it on the parameter instead of the argument.

Revision Notes

Key Takeaways

  • 1. infer captures positions, not names — it reuses whatever type landed there
  • 2. Distribution applies to inference too; wrap the checked type in a tuple to opt out
  • 3. Recursion is supported but bounded; prefer tail-recursive forms

Interview Tips

  • • Derive Awaited or ElementOf step by step and show the distributed result for a union input
  • • Contrast inference at a call site (runtime arguments) with inference in a conditional (type space)

Cheat Sheet

infer lives only in extends clauses; distributes over naked params; [T] disables it; Promise unwraps one layer; tail recursion ≤1000 steps.

Continue Learning