Skip to article
TypeScript 2 Feb 2026 10 min read 1.6K views

Advanced TypeScript Patterns: Conditional Types & Template Literals

Level up your type-safety game with inferred types, mapped types, and the dark arts of conditional type manipulation.

Suboor Khan

Full-Stack Developer & Technical Writer

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
  • infer extracts 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

Stay Updated

Enjoyed this article?

Deep-dive articles on React, AI, WebGL, and software craft — twice a month. No spam, ever.