TypeScript
05 / 18

Advanced Patterns

TypeScript Advanced Patterns

Conditional Types

// Basic conditional type
type IsString<T> = T extends string ? true : false;

type A = IsString<string>;  // true
type B = IsString<number>;  // false

// Practical example
type Flatten<T> = T extends Array<infer U> ? U : T;

type Str = Flatten<string>;    // string
type Num = Flatten<number[]>;  // number

// Distributive conditional types
type ToArray<T> = T extends any ? T[] : never;

type StrOrNum = string | number;
type Arrays = ToArray<StrOrNum>;  // string[] | number[]

// infer keyword
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function getUser() {
  return { id: 1, name: 'John' };
}

type UserType = GetReturnType<typeof getUser>;
// { id: number; name: string }

// Nested conditional types
type Unpromisify<T> = T extends Promise<infer U>
  ? U extends Promise<infer V>
    ? Unpromisify<V>
    : U
  : T;

type A = Unpromisify<Promise<string>>;  // string
type B = Unpromisify<Promise<Promise<number>>>;  // number

Mapped Types Advanced

// Make properties nullable
type Nullable<T> = {
  [P in keyof T]: T[P] | null;
};

interface User {
  id: number;
  name: string;
}

type NullableUser = Nullable<User>;
// { id: number | null; name: string | null }

// Get function properties
type FunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];

type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;

class MyClass {
  name: string = '';
  count: number = 0;
  
  method1() {}
  method2() {}
}

type Methods = FunctionProperties<MyClass>;
// { method1: () => void; method2: () => void }

// Deep readonly
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object
    ? DeepReadonly<T[P]>
    : T[P];
};

interface Config {
  database: {
    host: string;
    port: number;
  };
  cache: {
    ttl: number;
  };
}

type ReadonlyConfig = DeepReadonly<Config>;
// All nested properties are readonly

Type Challenges

// Deep Partial
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

// Awaited (built-in TypeScript utility)
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;

type A = Awaited<Promise<string>>;  // string
type B = Awaited<Promise<Promise<number>>>;  // number

// Required keys
type RequiredKeys<T> = {
  [K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];

interface Example {
  a: string;
  b?: number;
  c: boolean;
}

type ReqKeys = RequiredKeys<Example>;  // 'a' | 'c'

// Readonly keys
type ReadonlyKeys<T> = {
  [K in keyof T]-?: (<F>() => F extends { [Q in K]: T[K] } ? 1 : 2) extends
    (<F>() => F extends { -readonly [Q in K]: T[K] } ? 1 : 2)
      ? never
      : K;
}[keyof T];

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free