TypeScript's type system is Turing-complete. That's not just a party trick—it means you can encode complex business logic at compile time, catching an entire class of runtime bugs before they ship. Most teams use 20% of TypeScript's power and leave 80% on the table.
In this article we'll cover conditional types, the infer keyword, template literal types, and mapped types—and combine them into genuinely useful production utilities.
Conditional Types
Conditional types follow T extends U ? X : Y — if T is assignable to U, resolve to X, otherwise Y. They distribute over union types automatically.
// Without conditional types
type IsString = string extends string ? 'yes' : 'no'; // 'yes'
type IsNumber = number extends string ? 'yes' : 'no'; // 'no'
// Distributive — each member of the union is evaluated separately
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>; // string[] | number[]
// Non-distributive (wrap in tuple to prevent distribution)
type NonDistrib<T> = [T] extends [any] ? T[] : never;
type Result2 = NonDistrib<string | number>; // (string | number)[]
The Infer Keyword
infer lets you capture a type within a conditional type's extends clause. It's the key to extracting types from other types.
// Extract return type of any function
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// Extract Promise value
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
// Extract first argument
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;
// Extract array element type
type ElementType<T> = T extends (infer E)[] ? E : never;
type E = ElementType<string[]>; // string
// Real-world: infer the shape of API responses
type ApiData<T extends (...args: any) => Promise<Response>> =
Awaited<ReturnType<T>> extends Response ?
ReturnType<Response['json']> : never;
Template Literal Types
Template literal types combine string unions into new string patterns. They're invaluable for typed event systems, CSS property names, and API route generation.
type Breakpoint = 'sm' | 'md' | 'lg' | 'xl';
type Axis = 'x' | 'y';
type Direction = 'top' | 'right' | 'bottom' | 'left';
// Generate padding utility classes
type PaddingClass = `p${Axis}-${number}` | `p${Direction[0]}-${number}`;
// Typed event names
type EventName<T extends string> = `on${Capitalize<T>}`;
type ButtonEvents = EventName<'click' | 'hover' | 'focus'>;
// → 'onClick' | 'onHover' | 'onFocus'
// Extract segments from route strings
type RouteParams<T extends string> =
T extends `${infer _}/:${infer Param}/${infer Rest}`
? Param | RouteParams<`/${Rest}`>
: T extends `${infer _}/:${infer Param}`
? Param : never;
type Params = RouteParams<'/users/:userId/posts/:postId'>;
// → 'userId' | 'postId'
Mapped Types
Mapped types transform every property of a type according to a rule. Combined with conditional types they become extremely powerful.
// Make all props optional and nullable
type Nullable<T> = { [K in keyof T]: T[K] | null };
type Optional<T> = { [K in keyof T]?: T[K] };
// Filter props by type
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K]
};
interface User {
id: number;
name: string;
email: string;
age: number;
}
type StringProps = PickByType<User, string>;
// → { name: string; email: string }
// Deep readonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]
};
Building Real Utilities
Combining everything: a fully type-safe API client where endpoints and return types are inferred from a route definition object.
type Routes = {
'/users': { method: 'GET'; body: never; response: User[] };
'/users/:id': { method: 'GET'; body: never; response: User };
'/users/:id/posts': { method: 'POST'; body: CreatePost; response: Post };
};
type ApiClient = {
[R in keyof Routes]: Routes[R]['method'] extends 'GET'
? () => Promise<Routes[R]['response']>
: (body: Routes[R]['body']) => Promise<Routes[R]['response']>
};
// autocomplete knows the return type of each endpoint ✓
Summary
- Conditional types let you encode if/else logic in the type system
inferextracts sub-types from complex generics- Template literal types generate string union types from combinations
- Mapped types transform every property of a type systematically
- Combine all four to build zero-runtime-cost type-safe utilities