TypeScript Advanced Types

A comprehensive guide to conditional types, mapped types, template literals, and type inference with infer.

TL;DR

  1. Build type-level ternary conditions using the extends ? : syntax.
  2. Extract nested types automatically with the conditional infer keyword.
  3. Transform object keys dynamically using mapped type as remapping clauses.

Conditional Types

    ternary syntax

    Evaluates type-level branching based on contract assignability.

    type IsString<T> = T extends string ? true : false;
    type A = IsString<'hello'>; // true
    type B = IsString<number>;  // false
    distributive conditional

    Applies conditional logic across each member of a union.

    type Box<T> = T extends any ? { val: T } : never;
    type Boxed = Box<string | number>;
    // { val: string } | { val: number }
    preventing distribution

    Wraps type in a tuple to evaluate unions as a single entity.

    type NonDistributive<T> = [T] extends [string]
      ? true
      : false;
    type Check = NonDistributive<string | number>; // false

Type Inference with Infer

    infer return type

    Extracts the return type of an arbitrary function signature.

    type GetReturn<T> = T extends (
      ...args: any[]
    ) => infer R ? R : never;
    type Num = GetReturn<() => number>; // number
    unwrap Promise

    Extracts the resolved payload type from Promise instances.

    type Unwrap<T> = T extends Promise<infer U> ? U : T;
    type Resolved = Unwrap<Promise<string>>; // string
    tuple element extraction

    Pulls out the first element type from fixed tuple arrays.

    type Head<T> = T extends [infer First, ...any[]]
      ? First
      : never;
    type First = Head<[string, number]>; // string

Mapped Types

    mapped key iteration

    Iterates over property keys of an object to transform types.

    type Stringify<T> = {
      [K in keyof T]: string;
    };
    type Base = { age: number; active: boolean };
    type Form = Stringify<Base>;
    modifier subtraction

    Removes readonly and optional flags with minus prefixes.

    type Concrete<T> = {
      -readonly [K in keyof T]-?: T[K];
    };
    filtering with never

    Excludes properties whose keys map to the never type.

    type OnlyStrings<T> = {
      [K in keyof T as
        T[K] extends string ? K : never]: T[K];
    };

Key Remapping with As

    getter remapping

    Generates getter function signatures with capitalized keys.

    type Getters<T> = {
      [K in keyof T as `get${Capitalize<string & K>}`]:
        () => T[K];
    };
    type PersonGetters = Getters<{ name: string }>;
    prefixing keys

    Prepends a common namespace prefix to all property keys.

    type Prefixed<T, P extends string> = {
      [K in keyof T as `${P}_${string & K}`]: T[K];
    };
    filter by value type

    Keeps only properties whose values match a given constraint.

    type MethodsOnly<T> = {
      [K in keyof T as T[K] extends Function ? K : never]:
        T[K];
    };

Template Literal Types

    string union cross-product

    Combines string unions into combinatorial variations.

    type Event = 'click' | 'focus';
    type State = 'active' | 'idle';
    type EventState = `${Event}_${State}`;
    // 'click_active' | 'click_idle' | ...
    intrinsic string helpers

    Transforms casing with Uppercase and Capitalize helpers.

    type Shout = Uppercase<'hello'>; // 'HELLO'
    type Cap = Capitalize<'name'>;    // 'Name'
    pattern matching strings

    Extracts substrings dynamically using infer inside literals.

    type TrimPrefix<T> = T extends `prefix_${infer Rest}`
      ? Rest
      : T;
    type Clean = TrimPrefix<'prefix_item'>; // 'item'

Tips

  1. Wrap naked type parameters in single-element tuples like [T] to prevent distributive behavior across union types.
  2. Combine template literal types with Capitalize to auto-generate type-safe getter and setter signatures from existing property names.

Warnings

  1. Avoid unbounded recursive conditional types using infer because exceeding the compiler recursion limit causes fatal stack overflow errors.
  2. Remember that mapped types filtering keys with never only remove keys when using the as key remapping clause.

In Practice

FAQ