Conditional Types: Ternary Logic for Your Types
Conditional types are like a ternary operator for your type system, choosing a type based on a condition (`T extends U ? X : Y`). They're used with generics to make a function's return type depend on its input, avoiding cumbersome function overloads.
WHY IT EXISTS JavaScript code often makes decisions based on the type of an input. TypeScript needs a way to model the relationship between input types and output types without writing an explosion of function overloads for every possible combination, which becomes unmanageable as complexity grows.
THE MENTAL MODEL Think of a ternary operator (condition ? trueValue : falseValue), but for types. A conditional type asks a question—'is this type assignable to that type?'—and gives you one of two other types based on the answer. The syntax is SomeType extends OtherType ? TrueType : FalseType;.
HOW IT WORKS When TypeScript evaluates a conditional type, it checks if the type on the left of the extends keyword can be assigned to the type on the right. If it can, the result is the first type (TrueType). If not, the result is the second type (FalseType). The real power emerges when the type being checked is a generic parameter, like T. This makes the resulting type dynamic, depending on what T is instantiated with when a function is called.
WHEN TO USE IT Use conditional types to create flexible, reusable utility types that reduce boilerplate. They are ideal for simplifying functions that would otherwise require many overloads. For example, a function that accepts a string or a number and returns a different object shape for each can be written as a single generic function using a conditional type for its return value. This is a common pattern in library design for creating cleaner, more scalable APIs.
WHEN NOT TO USE IT Avoid using them for simple, non-generic checks where the outcome is already obvious, like string extends number ? A : B. While technically valid, it's just a more complex way of writing the resulting type B directly. Their purpose is to handle uncertainty in generic contexts, not to over-engineer static type definitions.
ONE CANONICAL EXAMPLE Instead of writing multiple overloads for a createLabel function like function createLabel(id: number): IdLabel; and function createLabel(name: string): NameLabel;, you can define a single conditional type: type LabelType<T extends string | number> = T extends number ? IdLabel : NameLabel;. Then, the function becomes one clean, generic implementation: function createLabel<T extends string | number>(input: T): LabelType<T> { /* ... */ }.
Read the original → typescriptlang.org
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.