TypeScript
10 / 18

TypeScript Advanced Types

TypeScript Advanced Types

Generics

// Generic functions
function identity<T>(arg: T): T { return arg; }
function first<T>(arr: T[]): T | undefined { return arr[0]; }

// Constrained generics
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
getProperty({ name: 'Alice', age: 30 }, 'name');  // OK
getProperty({ name: 'Alice' }, 'foo');             // Error

// Multiple type params
function zip<A, B>(a: A[], b: B[]): [A, B][] {
  return a.map((item, i) => [item, b[i]]);
}

// Generic classes
class Repository<T extends { id: string }> {
  private items: T[] = [];
  findById(id: string): T | undefined {
    return this.items.find(i => i.id === id);
  }
  save(item: T): void { this.items.push(item); }
}

// Generic interfaces
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}
type UserResponse = ApiResponse<User>;
type ListResponse<T> = ApiResponse<{ items: T[]; total: number }>;

// Default generic params
interface State<T = unknown> { value: T; loading: boolean; }

Utility Types

interface User {
  id: string;
  name: string;
  email: string;
  age?: number;
  role: 'admin' | 'user';
}

// Built-in utility types
Partial<User>           // all props optional
Required<User>          // all props required
Readonly<User>          // all props readonly
Pick<User, 'id'|'name'> // subset of props
Omit<User, 'role'>      // exclude props
Record<string, User>    // key → value map
Exclude<'a'|'b'|'c', 'a'>  // 'b' | 'c'
Extract<'a'|'b'|'c', 'a'|'b'>  // 'a' | 'b'
NonNullable<string|null|undefined>  // string
ReturnType<typeof fn>   // return type of fn
Parameters<typeof fn>   // param types as tuple
InstanceType<typeof MyClass>
Awaited<Promise<string>>  // string

// Custom utility types
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

type Mutable<T> = { -readonly [K in keyof T]: T[K] };

type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
// Usage: Optional<User, 'age' | 'role'> — makes those fields optional

type CreateInput<T> = Omit<T, 'id' | 'createdAt' | 'updatedAt'>;

Conditional & Mapped Types

// Conditional types
type IsString<T> = T extends string ? true : false;
type IsArray<T> = T extends any[] ? true : false;

// infer — extract type within conditional
type UnwrapPromise<T> = T extends Promise<infer R> ? R : T;
type ArrayElement<T> = T extends (infer E)[] ? E : never;

type UnwrapPromise<T> = T extends Promise<infer R> ? UnwrapPromise<R> : T;
// UnwrapPromise<Promise<Promise<string>>> → string

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

// Mapped types
type Nullable<T> = { [K in keyof T]: T[K] | null };
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] };
// Getters<{name: string}> → { getName: () => string }

// Template literal types
type EventName<T extends string> = `on${Capitalize<T>}`;
type HttpMethod = 'get' | 'post' | 'put' | 'delete';
type ApiRoute = `/api/${string}`;

// Discriminated unions
type Result<T> =
  | { success: true; data: T }
  | { success: false; error: string };

function processResult<T>(result: Result<T>) {
  if (result.success) {
    console.log(result.data);   // T — TypeScript knows this
  } else {
    console.log(result.error);  // string
  }
}

Decorators & Advanced Patterns

// satisfies operator (TS 4.9) — validate type without widening
const palette = {
  red: [255, 0, 0],
  green: '#00ff00',
} satisfies Record<string, string | number[]>;
palette.red.map(...)    // OK — type is number[], not string | number[]

// const assertion
const routes = ['/', '/about', '/contact'] as const;
type Route = typeof routes[number];  // '/' | '/about' | '/contact'

const config = { host: 'localhost', port: 3000 } as const;
// config.port is 3000 (literal), not number

// Function overloads
function parse(val: string): number;
function parse(val: number): string;
function parse(val: string | number): string | number {
  return typeof val === 'string' ? parseInt(val) : val.toString();
}

// Module augmentation
declare module 'express' {
  interface Request {
    user?: { id: string; role: string };
  }
}

// Branded types — prevent mixing compatible primitives
type UserId = string & { readonly __brand: 'UserId' };
type OrderId = string & { readonly __brand: 'OrderId' };
const toUserId = (id: string): UserId => id as UserId;
// Can't pass OrderId where UserId expected

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

Start free