MeshWorld India Logo MeshWorld.
typescript generics types skills web-development 5 min read

TypeScript Generics & Advanced Types Cheatsheet: The Complete Reference

Scarlett
By Scarlett
TypeScript Generics & Advanced Types Cheatsheet: The Complete Reference

TypeScript provides a sophisticated static type system that enables developers to build highly flexible and type-safe components. By unlocking advanced type mechanics, you can eliminate structural duplication and verify runtime constraints at compile time.

This reference sheet covers generics, utility type functions, conditional structures, mapped modifiers, template literal types, and custom type guards.


- **Generics**: Write reusable components that preserve type relationships using generic parameters and constraints (`extends`). - **Utility Types**: Leverage standard tools like `Pick`, `Omit`, `ReturnType`, and `Record` to quickly transform schemas. - **Conditional Types**: Dynamically resolve types at compile time using standard ternary-like logic (`T extends U ? X : Y`). - **Mapped Types**: Dynamically iterate over keys to construct new interface modifications. - **Template Literals**: Manipulate string types to enforce routing paths or event naming conventions. - **Type Guards**: Create custom type checkers using the `is` keyword to guarantee type safety in runtime branches.

Before diving into this cheatsheet, check out my previous deep-dive on Hono Edge Web Framework Cheatsheet: The Complete Reference to see how we structured these patterns in practice.

Mastering Generics

Generics allow you to write algorithms and structures that operate on variable types while maintaining absolute type fidelity.

// 1. Generic Functions with Constraints
interface Identifiable {
  id: string | number;
}

function getRecordById<T extends Identifiable>(records: T[], id: T['id']): T | undefined {
  return records.find(item => item.id === id);
}

// 2. Generic Interfaces and Defaults
interface ApiResponse<Data = Record<string, unknown>, Meta = Record<string, unknown>> {
  status: 'success' | 'error';
  data: Data;
  meta?: Meta;
}

interface UserProfile {
  username: string;
  email: string;
}

const response: ApiResponse<UserProfile> = {
  status: 'success',
  data: {
    username: 'dev_scarlett',
    email: 'scarlett@meshworld.in'
  }
};

Leveraging Built-in Utility Types

TypeScript comes equipped with standard utility types designed to transform existing interfaces.

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

// Make all fields optional (ideal for update payloads)
type PartialUser = Partial<User>;

// Make all fields mandatory
type CompleteUser = Required<User>;

// Make all fields immutable
type ImmutableUser = Readonly<User>;

// Construct a type consisting of select properties
type UserContactInfo = Pick<User, 'name' | 'email'>;

// Construct a type by removing select properties
type UserWithoutCredentials = Omit<User, 'id' | 'role'>;

// Maps keys of one type to another type
type UserDirectory = Record<string, User>;

Harnessing Conditional Types

Conditional types select one of two possible types based on a constraint check, utilizing syntax similar to a ternary operator.

// Syntax: T extends U ? X : Y

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

// Dynamic API Response Payload resolution
interface TextMessage {
  text: string;
}

interface ImageMessage {
  imageUrl: string;
}

type MessagePayload<T extends 'text' | 'image'> = T extends 'text' 
  ? TextMessage 
  : ImageMessage;

function createMessage<T extends 'text' | 'image'>(type: T, payload: MessagePayload<T>) {
  return { type, payload };
}

The infer Keyword

Use infer within a conditional type to declare a placeholder type variable to be extracted.

// Extract the resolved value of a Promise
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type ResolvedString = UnwrapPromise<Promise<string>>; // string
type StandardNumber = UnwrapPromise<number>;          // number

Operating Mapped Types

Mapped types allow you to create new types based on the keys of an existing type by iterating over them.

interface FeatureFlags {
  enableSearch: boolean;
  enableAnalytics: boolean;
  enableBetaDashboard: boolean;
}

// Transform all property types to functions returning that type
type FeatureGetters<T> = {
  [K in keyof T]: () => T[K];
};

type AppFeatureFlags = FeatureGetters<FeatureFlags>;
/*
  Resolves to:
  {
    enableSearch: () => boolean;
    enableAnalytics: () => boolean;
    enableBetaDashboard: () => boolean;
  }
*/

// Modify properties: remove readonly modifiers and make all optional
type MutablePartial<T> = {
  -readonly [K in keyof T]?: T[K];
};

Constructing Template Literal Types

Template literal types build on string literal types, allowing you to combine and manipulate strings at the type level.

type Protocol = 'http' | 'https';
type Port = 80 | 443 | 8080;

// Generate all permutations of valid local addresses
type LocalUrl = `${Protocol}://localhost:${Port}`;
// Resolves to: "http://localhost:80" | "http://localhost:443" | ...

// Auto-prefix event types
type Event = 'click' | 'hover' | 'submit';
type OnEvent = `on${Capitalize<Event>}`;
// Resolves to: "onClick" | "onHover" | "onSubmit"

Implementing Type Guards & Assertions

Type guards allow you to narrow down the type of an object within a conditional block using standard type predicate syntax.

interface Admin {
  name: string;
  privileges: string[];
}

interface Editor {
  name: string;
  sections: string[];
}

type Staff = Admin | Editor;

// 1. Custom Type Guard using 'is'
function isAdmin(staff: Staff): staff is Admin {
  return (staff as Admin).privileges !== undefined;
}

function executeStaffAction(staff: Staff) {
  if (isAdmin(staff)) {
    // TypeScript now knows 'staff' is strictly Admin here
    console.log(`Admin privileges: ${staff.privileges.join(', ')}`);
  } else {
    // TypeScript knows 'staff' is strictly Editor here
    console.log(`Editor assigned sections: ${staff.sections.join(', ')}`);
  }
}

// 2. Custom Type Assertion using 'asserts'
function assertIsString(value: unknown): asserts value is string {
  if (typeof value !== 'string') {
    throw new Error('Value must be a string');
  }
}

function processInput(input: unknown) {
  assertIsString(input);
  // input is typed as string from this point forward
  console.log(input.toUpperCase());
}