3 Topics
Chapter•85 Minutes

Advanced Types: Conditional Types, infer, and Template Literals

Learn conditional types, inference within types, template literal types, and recursive type patterns.

Conditional Types

A conditional type has the form T extends U ? X : Y. It selects a type based on assignability. When the checked type is a naked type parameter, conditional types distribute over unions: ToArray<string | number> becomes string[] | number[] if defined as T extends unknown ? T[] : never.

Distribution is powerful but can surprise you. Wrapping the checked type in a tuple—[T] extends [U]—checks the union as a whole rather than distributing. Use conditional types when the relationship is reusable and the result is clearer than multiple overloads.

Conditional type example
type ToArray<T> = T extends unknown ? T[] : never;
type Distributed = ToArray<string | number>;
// string[] | number[]

type WholeUnionCheck<T> = [T] extends [string] ? true : false;
type Check = WholeUnionCheck<string | number>; // false
💡 The first conditional distributes because T is naked on the left of extends. The tuple wrapper suppresses distribution and evaluates the union as one type.
Key Takeaways
  • Conditional types choose a type based on assignability.
  • Naked type parameters distribute over unions.
  • Tuple wrapping can suppress distributive behavior.
Knowledge Check

When does a conditional type distribute over a union?

The infer Keyword

infer introduces a type variable inside a conditional type pattern. It lets you extract parts of a type, such as a function's return type, an array element type, or a promise's resolved type. Built-in utility types such as ReturnType and Awaited use related patterns.

Keep the pattern aligned with the shape you want to inspect. For overloaded functions, ReturnType generally uses the last signature; do not assume it represents every overload separately. Type-level inference is compile-time computation and does not call the function or inspect a runtime value.

Extract a promise's resolved value
type Resolved<T> = T extends Promise<infer Value> ? Value : T;

type A = Resolved<Promise<string>>; // string
type B = Resolved<number>;          // number
💡 If T matches Promise<...>, infer Value captures the inner type. Otherwise the conditional returns T unchanged.
Key Takeaways
  • infer captures a type within a conditional pattern.
  • It operates on types, not runtime values.
  • Built-in utility types use similar extraction patterns.
Knowledge Check

What does `infer Value` do in `T extends Promise<infer Value>`?

Template Literal Types

Template literal types construct string literal types from other types. They can model event names, route patterns, CSS property names, and generated API keys. Combined with unions and mapped types, they allow type-safe relationships between names and values.

They are compile-time constraints, not string parsers at runtime. For dynamic strings from users or external services, validate the value at runtime even if a template literal type describes the expected pattern.

Type-safe event names
type Field = "name" | "email";
type ChangeEvent = `${Field}Changed`;
// "nameChanged" | "emailChanged"

function onChange(event: ChangeEvent) {
  console.log(event);
}
💡 TypeScript combines each member of Field with the suffix, producing a union of two valid event names.
Key Takeaways
  • Template literal types compose string literal types.
  • They can derive event names from domain keys.
  • Runtime strings still need validation at trust boundaries.
Knowledge Check

What is the resulting type of `${"get" | "set"}${"Name" | "Age"}`?