technology · typescript

TypeScript Cheatsheets

KDP Book Manifest & Metadata
Click to Expand & CopyManifest

Use the copy buttons below to copy metadata verbatim into the Amazon KDP Publishing forms.

Book Title
Subtitle
Target Audience
BISAC Subject Code
Keywords (Comma Separated)
Book Description (HTML)
KDP Categories
  • Books > Computers & Technology > Programming > TypeScript
  • Books > Computers & Technology > Web Development

TypeScript Cheatsheets

One-Page Quick References from Core Syntax to Advanced Patterns

usefulcheatsheets.com

TypeScript Cheatsheets

First Edition: 2026

Copyright © 2026 by usefulcheatsheets.com. All rights reserved.

No part of this book may be reproduced in any form or by any electronic or mechanical means, including information storage and retrieval systems, without written permission from the publisher, except for the use of brief quotations in a book review.

Publisher: usefulcheatsheets.comISBN: Not ApplicableBISAC Subject Code: COM051000

Welcome to TypeScript

TypeScript is a key topic in Technology development.

This reference book compiles comprehensive cheatsheets covering everything from fundamentals to advanced patterns.

Use this book as a daily reference or read it linearly to build your knowledge.

How to Use This Book

Each page is a visual cheatsheet with core concepts, practical steps, code snippets, and warnings.

usefulcheatsheets.com | Introduction
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Basics
Chapter 01 · Page 7
Beginner

TypeScript Basics

A starter guide to TypeScript covering types, variables, and basic syntax for newcomers.

TL;DR

  1. 01Add static types to your JavaScript variables and functions.
  2. 02Catch bugs at compile time before code runs.
  3. 03Use the TypeScript compiler to convert to JavaScript.

Tips

  1. 01Let TypeScript infer types when the value is obvious to keep your code clean and readable.

Warnings

  1. 01Avoid the <code>any</code> type whenever possible, since it removes the type safety that TypeScript provides.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Basics
Chapter 01 · Page 8
Beginner

TypeScript Basics

(continued)

Setup

  • Install TypeScript globally with npm to get the tsc command.
    npm install -g typescript
  • Run tsc --init to create a tsconfig.json file for your project.
    tsc --init
  • Save your code in files with the .ts extension instead of .js.
  • Compile your TypeScript file by running the compiler with the filename.
    tsc filename.ts
  • Run the generated JavaScript file with Node or in the browser.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Basics
Chapter 01 · Page 9
Beginner

TypeScript Basics

(continued)

Basic Types

  • Use string for text values like names, messages, or any words.
    const name: string = "Sam";
  • Use number for integers and decimals, since there is no separate float type.
    const age: number = 30;
    const price: number = 9.99;
  • Use boolean for true and false values in conditions and flags.
    const isActive: boolean = true;
  • Use any to disable type checking, but try to avoid it.
    let data: any = 42;
    data = "now a string"; // allowed
  • Use unknown for safer code when the type is not yet known.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Basics
Chapter 01 · Page 10
Beginner

TypeScript Basics

(continued)

Variables

  • Declare typed variables with an annotation after the variable name.
    let username: string = "Sam";
  • Skip the annotation and let TypeScript infer the type from the value.
    let count = 5; // inferred as number
  • Use const for values that never change, like config keys or fixed numbers.
    const MAX_RETRIES = 3;
  • Declare arrays using square brackets or the generic Array form.
    const scores: number[] = [10, 20, 30];
    const names: Array<string> = ["A", "B"];
  • Use tuples for arrays with fixed types and length.
    const point: [number, number] = [10, 20];
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Basics
Chapter 01 · Page 11
Beginner

TypeScript Basics

(continued)

Functions

  • Add types to parameters and a return type for full safety.
    function add(a: number, b: number): number {
      return a + b;
    }
  • Use void as the return type for functions that do not return anything.
    function log(msg: string): void {
      console.log(msg);
    }
  • Mark optional parameters with a question mark after the name.
    function greet(name?: string) {
      return `Hi ${name ?? "friend"}`;
    }
  • Set default parameter values directly in the function signature.
    function multiply(a: number, b: number = 2) {
      return a * b;
    }
  • Type arrow functions the same way as regular functions for consistency.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Basics
Chapter 01 · Page 12
Beginner

TypeScript Basics

(continued)

Compiler

  • Edit tsconfig.json to control how the TypeScript compiler builds your code.
  • Enable "strict": true to turn on all strict type-checking options at once.
    {
      "compilerOptions": {
        "strict": true,
        "target": "ES2022"
      }
    }
  • Use "target" to choose which JavaScript version the compiler outputs.
  • Run tsc --watch to recompile files every time you save changes.
    tsc --watch
  • Check the outDir setting to control where compiled JavaScript files are saved.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Basics
Chapter 01 · Page 13
Beginner

TypeScript Basics

(FAQ)

FAQ

Run npm install -g typescript to install the compiler globally, then use tsc --init in your project folder to generate a tsconfig.json with sensible defaults. From there, run tsc to compile your .ts files to JavaScript.

Both can describe object shapes, but interface supports declaration merging and is generally preferred for defining object structures, while type is more flexible and can represent unions, intersections, and primitives. Use interface for class contracts and public APIs, type for everything else.

Mark a parameter optional with a ? suffix (e.g., name?: string), which makes it string | undefined inside the function. For defaults, use standard JavaScript syntax (name: string = 'guest'), which also makes the argument optional at the call site without the undefined type.

unknown is the type-safe alternative: you can assign anything to it, but TypeScript forces you to narrow the type before using it, preventing accidental runtime errors. Reach for unknown when you genuinely don't know the type upfront, such as parsing JSON or handling catch clause errors.

TypeScript widens literal values (e.g., let x = 'hello' becomes string, not 'hello') to allow reassignment. Use const instead of let to keep the literal type, or add as const to freeze an object or array's inferred types to their exact literal values.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Classes
Chapter 02 · Page 14
Beginner

TypeScript Classes

A guide to TypeScript classes, constructors, access modifiers, and inheritance with practical examples.

TL;DR

  1. 01Create classes with typed properties and a constructor.
  2. 02Control access with public, private, and protected modifiers.
  3. 03Extend classes to share behavior through inheritance.

Tips

  1. 01Prefer parameter properties in constructors to cut boilerplate, since they declare and assign in a single line.

Warnings

  1. 01Use the <code>#</code> prefix for true privacy when needed, since the <code>private</code> keyword is only enforced at compile time.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Classes
Chapter 02 · Page 15
Beginner

TypeScript Classes

(continued)

Basic Syntax

  • Declare a class with the class keyword and a PascalCase name.
    class User {
      name: string;
      constructor(name: string) {
        this.name = name;
      }
    }
  • Declare typed properties at the top of the class body for clarity.
  • The constructor runs automatically when you create a new instance.
    const sam = new User("Sam");
  • Use this inside methods to refer to the current instance.
  • Methods are typed just like regular functions with parameters and return types.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Classes
Chapter 02 · Page 16
Beginner

TypeScript Classes

(continued)

Constructor Shortcuts

  • Add modifiers to constructor parameters to create and assign properties at once.
    class User {
      constructor(public name: string, public age: number) {}
    }
  • This shortcut saves you from writing property declarations separately.
  • Combine with readonly to make the property immutable after construction.
    class Point {
      constructor(readonly x: number, readonly y: number) {}
    }
  • Use default values in the constructor for optional inputs.
  • Parameter properties work with public, private, protected, and readonly.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Classes
Chapter 02 · Page 17
Beginner

TypeScript Classes

(continued)

Access Modifiers

  • Use public for properties that anyone can read or change.
    class User {
      public name: string = "Sam";
    }
  • Use private to hide properties from code outside the class.
    class Account {
      private balance: number = 0;
    }
  • Use protected to allow access from the class and its subclasses only.
  • The # prefix gives true runtime privacy that survives compilation.
    class Counter {
      #count = 0;
      next() { return ++this.#count; }
    }
  • Default modifier is public when none is specified.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Classes
Chapter 02 · Page 18
Beginner

TypeScript Classes

(continued)

Inheritance

  • Use the extends keyword to create a subclass of another class.
    class Animal {
      move() { console.log("moving"); }
    }
    class Dog extends Animal {
      bark() { console.log("woof"); }
    }
  • Call super() inside the constructor to run the parent constructor first.
    class Cat extends Animal {
      constructor(public name: string) {
        super();
      }
    }
  • Override parent methods by redeclaring them in the subclass.
  • Use super.method() to call the parent method from inside an override.
  • A class can extend only one parent — use interfaces for multiple contracts.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Classes
Chapter 02 · Page 19
Beginner

TypeScript Classes

(continued)

Abstract and Static

  • Mark a class abstract to prevent direct instantiation.
    abstract class Shape {
      abstract area(): number;
    }
  • Subclasses must implement every abstract method to be usable.
  • Use static to attach properties or methods to the class itself, not instances.
    class MathHelper {
      static double(n: number) { return n * 2; }
    }
    MathHelper.double(5); // 10
  • Static methods are great for utility functions related to the class.
  • Use implements to require a class to match an interface contract.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Classes
Chapter 02 · Page 20
Beginner

TypeScript Classes

(FAQ)

FAQ

Abstract classes can contain implemented methods and constructor logic, while interfaces only define shape contracts with no implementation. Use abstract classes when you want to share code across subclasses; use interfaces when you only need to enforce a structure.

Static members belong to the class itself rather than any instance, so you access them via the class name (e.g., MyClass.count). They're useful for factory methods, counters, or shared utilities that don't depend on instance state.

Use protected when subclasses need direct access to a member, and private when you want to completely hide implementation details from child classes. Choosing protected too liberally can couple subclasses tightly to parent internals, making refactoring harder.

Use super() in the subclass constructor before accessing this, and super.methodName() to invoke an overridden parent method. Forgetting to call super() in a derived constructor is a runtime error, not just a type error.

Yes — a class can implement multiple interfaces by separating them with commas (e.g., class Foo implements Bar, Baz). This is the standard way to satisfy multiple contracts since TypeScript only supports single class inheritance.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Enums
Chapter 03 · Page 21
Beginner

TypeScript Enums

Use enums effectively for type-safe constants and fixed sets of values.

TL;DR

  1. 01Prefer string enums over numeric ones for self-documenting, debuggable code.
  2. 02Numeric enums support reverse mapping but can be confusing.
  3. 03Const enums inline values at compile time for smaller bundles.

Tips

  1. 01Use string enums or union types for new code — they're clearer and don't have the reverse-mapping pitfalls of numeric enums.

Warnings

  1. 01Numeric enums can cause subtle bugs with reverse mapping — avoid them unless you specifically need the numeric values.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Enums
Chapter 03 · Page 22
Beginner

TypeScript Enums

(continued)

String Enums

  • Create a string enum to assign readable values to each member.

    enum Status {
      Active = "active",
      Inactive = "inactive",
      Pending = "pending"
    }
    
    const userStatus: Status = Status.Active; // "active"
    
  • Use string enums in switch statements for exhaustive, readable checks.

    function describe(status: Status): string {
      switch (status) {
        case Status.Active:   return "User is active";
        case Status.Inactive: return "User is inactive";
        case Status.Pending:  return "Awaiting approval";
      }
    }
    
  • Match API or database string values directly using string enum members.

    // API returns { status: "active" } — enum value matches exactly
    const fromApi: Status = "active" as Status;
    
  • Iterate over all string enum values using Object.values.

    const allStatuses = Object.values(Status);
    // ["active", "inactive", "pending"]
    
  • Use a string enum to keep role names self-documenting in conditions.

    enum Role { Admin = "admin", User = "user", Guest = "guest" }
    
    function checkRole(role: Role) {
      if (role === Role.Admin) console.log("is admin"); // "admin" in logs
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Enums
Chapter 03 · Page 23
Beginner

TypeScript Enums

(continued)

Numeric Enums

  • Create a numeric enum — members auto-increment from zero by default.

    enum Level {
      Low,    // 0
      Medium, // 1
      High    // 2
    }
    
  • Set explicit values to control what numbers each member maps to.

    enum Direction {
      Up = 0,
      Right = 1,
      Down = 2,
      Left = 3
    }
    
    const dir: Direction = Direction.Up; // 0
    
  • Use reverse mapping to look up a member name from its numeric value.

    enum Status { Active = 1, Inactive = 2 }
    
    console.log(Status[1]);      // "Active"
    console.log(Status.Active);  // 1
    
  • Use bit flags with left-shift to combine permissions in a single number.

    enum Permission {
      None  = 0,
      Read  = 1 << 0, // 1
      Write = 1 << 1, // 2
      Admin = 1 << 2  // 4
    }
    
    const userPerms = Permission.Read | Permission.Write; // 3
    
  • Start auto-increment from 1 to avoid zero-falsy truthiness bugs.

    enum Priority {
      Low = 1, // starts at 1, not 0
      Medium,  // 2
      High     // 3
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Enums
Chapter 03 · Page 24
Beginner

TypeScript Enums

(continued)

Const Enums

  • Declare a const enum to inline every member as a literal at compile time.

    const enum Color {
      Red = "red",
      Green = "green",
      Blue = "blue"
    }
    
    const myColor: Color = Color.Red;
    // Compiles to: const myColor = "red";
    
  • Verify zero runtime overhead — no JS object is generated in the bundle.

    const enum Direction { Up, Down, Left, Right }
    const dir = Direction.Up;
    // Compiles to: const dir = 0; — no Direction object exists at runtime
    
  • Show the size difference between regular and const enums.

    // Regular enum: creates a runtime object (adds ~200 bytes)
    // Const enum: replaces each use with its literal value (adds 0 bytes)
    const enum Size { Small = "sm", Large = "lg" }
    const s: Size = Size.Small; // becomes: const s = "sm";
    
  • Use string const enums safely when sharing across module boundaries.

    // Safe for cross-module use with string values
    const enum HttpMethod {
      Get = "GET",
      Post = "POST",
      Put = "PUT"
    }
    
  • Avoid const enums in .d.ts library files — consumers cannot inline them.

    // In a .d.ts file, prefer a union type or regular enum
    export type HttpMethod = "GET" | "POST" | "PUT";
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Enums
Chapter 03 · Page 25
Beginner

TypeScript Enums

(continued)

Comparing with Union Types

  • Use union types for simple value sets — they need no runtime code.

    type Status = "active" | "inactive" | "pending";
    
    const status: Status = "active"; // direct string, no enum import needed
    
  • Use an as const object to get both runtime values and a derived type.

    const STATUSES = {
      Active: "active",
      Inactive: "inactive"
    } as const;
    
    type Status = typeof STATUSES[keyof typeof STATUSES];
    // Status = "active" | "inactive"
    
  • Prefer union types when you need to serialize values to JSON cleanly.

    // Simpler, no runtime object, serializes as a plain string
    type Direction = "up" | "down" | "left" | "right";
    
  • Use enums when you need a named constant group importable as one symbol.

    // Useful when the group name matters for readability and logging
    enum LogLevel { Debug = "debug", Info = "info", Error = "error" }
    
    function log(level: LogLevel, msg: string) {
      console.log(`[${level}] ${msg}`);
    }
    
  • Combine an as const object with Object.values to iterate like an enum.

    const Roles = { Admin: "admin", User: "user", Guest: "guest" } as const;
    type Role = typeof Roles[keyof typeof Roles]; // "admin" | "user" | "guest"
    
    Object.values(Roles).forEach(r => console.log(r));
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Enums
Chapter 03 · Page 26
Beginner

TypeScript Enums

(continued)

Common Patterns

  • Use a numeric enum for HTTP status codes to keep values readable.

    enum HttpStatus {
      Ok = 200,
      Created = 201,
      BadRequest = 400,
      Unauthorized = 401,
      NotFound = 404
    }
    
  • Write a type guard to check if a runtime value is a valid enum member.

    function isValidStatus(value: unknown): value is Status {
      return Object.values(Status).includes(value as Status);
    }
    
  • Export all enums from a central file to share them across modules.

    // enums.ts
    export enum UserRole { Admin = "admin", User = "user" }
    
    // other-file.ts
    import { UserRole } from "./enums";
    
  • Use Record with an enum type to create exhaustive display label maps.

    const statusLabels: Record<Status, string> = {
      [Status.Active]:   "Active",
      [Status.Inactive]: "Inactive",
      [Status.Pending]:  "Pending"
    };
    
    console.log(statusLabels[Status.Active]); // "Active"
    
  • Use enums as discriminants in discriminated union types for events.

    enum EventType { Click = "click", KeyPress = "keypress" }
    
    type AppEvent =
      | { type: EventType.Click; x: number; y: number }
      | { type: EventType.KeyPress; key: string };
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Enums
Chapter 03 · Page 27
Beginner

TypeScript Enums

(FAQ)

FAQ

Prefer union types like type Direction = 'north' | 'south' for simple sets of values — they require no runtime code and are easier to extend. Use string enums when you want a named, iterable group of constants that appear as a single importable symbol across your codebase.

Numeric enums generate reverse mappings at runtime, so Object.keys(MyEnum) returns both the names and their numeric values (e.g., ['Up', 'Down', '0', '1']). Filter with isNaN or switch to string enums to avoid this pitfall.

A const enum is erased entirely at compile time — all usages are replaced with their literal values, producing no JavaScript object. Use it when bundle size matters and you don't need to iterate over or import the enum at runtime, but note it's incompatible with isolatedModules mode.

No — enums only support constant or computed member values, not methods. If you need behavior attached to enum values, use a plain object with as const or a class with static members instead.

Cast the enum to Record<string, string> and use Object.values: Object.values(MyEnum).includes(value as MyEnum). TypeScript won't narrow this automatically, so you'll need a type guard function that returns value is MyEnum for safe use downstream.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Enums Best Practices
Chapter 04 · Page 28
Beginner

TypeScript Enums Best Practices

Use enums effectively for type-safe constants and avoid common pitfalls.

TL;DR

  1. 01Use string enums for better debugging and serialization.
  2. 02Use const enums to avoid runtime overhead.
  3. 03Prefer union types over enums for simpler type definitions.

Tips

  1. 01Use union types with <code>as const</code> instead of enums for most cases — they're simpler and don't have the pitfalls of enum reverse mapping.

Warnings

  1. 01Numeric enums can cause subtle bugs due to reverse mapping — prefer string enums or union types to avoid confusion.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Enums Best Practices
Chapter 04 · Page 29
Beginner

TypeScript Enums Best Practices

(continued)

String Enums

  • Create enums with string values for clarity.
    enum Status {
      Active = "active",
      Inactive = "inactive",
      Pending = "pending"
    }
    
    const userStatus: Status = Status.Active;
  • String enums are self-documenting and easier to debug.
    // Logs "active" instead of "0" — readable in console and logs
    console.log(Status.Active); // "active"
  • Values are explicit and match database/API values.
    enum Role {
      Admin = "admin",
      User = "user",
      Guest = "guest"
    }
    
    function checkRole(role: Role) {
      if (role === Role.Admin) {
        // user is admin
      }
    }
  • Use string enums in switch statements for exhaustive type checking.
    function describe(s: Status): string {
      switch (s) {
        case Status.Active:   return "Active";
        case Status.Inactive: return "Inactive";
        case Status.Pending:  return "Pending";
      }
    }
  • Iterate string enum values safely with Object.values.
    const values = Object.values(Status);
    // ["active", "inactive", "pending"]
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Enums Best Practices
Chapter 04 · Page 30
Beginner

TypeScript Enums Best Practices

(continued)

Numeric Enums

  • Create enums with numeric values for flexibility.
    enum Direction {
      Up = 0,
      Right = 1,
      Down = 2,
      Left = 3
    }
    
    const dir: Direction = Direction.Up;
  • Numeric enums support reverse mapping.
    enum Status {
      Active = 1,
      Inactive = 2
    }
    
    console.log(Status[1]);  // "Active"
    console.log(Status.Active);  // 1
  • Auto-incrementing values reduce boilerplate.
    enum Level {
      Low,      // 0
      Medium,   // 1
      High      // 2
    }
  • Start numeric enums at 1 to avoid falsy zero bugs.
    enum Priority {
      Low = 1,  // avoids if (priority) being false for Low
      Medium,   // 2
      High      // 3
    }
  • Use bit flags with numeric enums for combined permissions.
    enum Permission {
      None  = 0,
      Read  = 1 << 0, // 1
      Write = 1 << 1, // 2
      Admin = 1 << 2  // 4
    }
    
    const canEdit = Permission.Read | Permission.Write; // 3
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Enums Best Practices
Chapter 04 · Page 31
Beginner

TypeScript Enums Best Practices

(continued)

Const Enums

  • Use const for enums that are inlined at compile time.
    const enum Color {
      Red = "red",
      Green = "green",
      Blue = "blue"
    }
    
    const myColor: Color = Color.Red;
  • Const enums don't generate runtime code.
    const enum Direction { Up, Down, Left, Right }
    const d = Direction.Up;
    // Compiles to: const d = 0; — no enum object in bundle
  • Smaller bundle size and faster performance.
    // Compiles to:
    const myColor = "red";
  • Only string values work reliably with const enums.
    // Use string values to stay safe across modules and bundlers
    const enum HttpMethod { Get = "GET", Post = "POST" }
  • Avoid const enums in published library declaration files.
    // Library consumers can't use const enums from .d.ts files
    // Use a regular enum or union type instead
    export type HttpMethod = "GET" | "POST" | "PUT";
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Enums Best Practices
Chapter 04 · Page 32
Beginner

TypeScript Enums Best Practices

(continued)

Heterogeneous Enums

  • Mix string and numeric values in a single enum.
    enum Mixed {
      No = 0,
      Yes = "YES"
    }
  • Avoid heterogeneous enums — they are hard to reason about.
    // Confusing: mixing types makes iteration and reverse lookup unpredictable
    enum Bad { A = 0, B = "b" }
  • Use separate enums or union types instead for clarity.
    // Better: separate enums
    enum NumericStatus {
      Inactive = 0,
      Active = 1
    }
    
    enum StringStatus {
      Inactive = "inactive",
      Active = "active"
    }
  • Keep all enum members the same type to enable consistent iteration.
    // Safe: all strings — Object.values returns only the values
    enum Color { Red = "red", Green = "green", Blue = "blue" }
    const colors = Object.values(Color); // ["red", "green", "blue"]
  • TypeScript emits a warning for most heterogeneous patterns.
    // Prefer string or numeric uniformly to avoid compiler warnings
    enum Uniform { A = "a", B = "b", C = "c" } // clean
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Enums Best Practices
Chapter 04 · Page 33
Beginner

TypeScript Enums Best Practices

(continued)

Union Types Alternative

  • Use union types instead of enums for simpler definitions.
    type Status = "active" | "inactive" | "pending";
    
    const status: Status = "active";
  • Union types are simpler and don't generate runtime code.
    // No object created — pure compile-time type
    type Role = "admin" | "user" | "guest";
  • Union types are more flexible for type checking and inference.
    type Role = "admin" | "user" | "guest";
    
    function hasAccess(role: Role): boolean {
      return role === "admin" || role === "user";
    }
  • Use as const to create both a type and runtime constants.
    const STATUSES = {
      Active: "active",
      Inactive: "inactive"
    } as const;
    
    type Status = typeof STATUSES[keyof typeof STATUSES];
    // Status = "active" | "inactive"
  • Prefer union types when values are already plain strings in your codebase.
    // API returns "GET" | "POST" directly — no enum needed
    type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
    
    function request(method: HttpMethod, url: string) { }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Enums Best Practices
Chapter 04 · Page 34
Beginner

TypeScript Enums Best Practices

(FAQ)

FAQ

Use string enums whenever enum values will appear in API payloads, logs, or serialized data — Status.Active staying as "active" at runtime is far easier to trace than 0. String enums also avoid the reverse-mapping behavior that numeric enums generate, keeping the compiled output clean.

A const enum is inlined at compile time — TypeScript replaces every usage with the literal value and emits no runtime object, reducing bundle size. However, they break under isolatedModules (used by Babel, esbuild, and Vite), so avoid them in library code or projects with those bundler setups.

Define an object with as const, then derive the value type using indexed access: const Status = { Active: 'active', Inactive: 'inactive' } as const; type Status = typeof Status[keyof typeof Status];. You get full autocomplete and type narrowing with zero runtime overhead and no reverse-mapping surprises.

TypeScript compiles numeric enums with both forward (Name → number) and reverse (number → Name) entries in the same object, so Object.keys(MyEnum) yields both string names and their numeric counterparts. Filter out entries where Number(key) is not NaN before iterating, or switch to string enums which skip the reverse mapping entirely.

No — heterogeneous enums combine the downsides of both types (reverse mapping from numeric members, inconsistent value shapes) while providing no advantage over two separate typed constants. Split them into distinct string enums or union types based on what each group of values actually represents.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Error Messages
Chapter 05 · Page 35
Beginner

TypeScript Error Messages

Understand common TypeScript errors and how to fix them.

TL;DR

  1. 01Type mismatch errors indicate your value doesn't match the declared type.
  2. 02Cannot find name errors mean the variable doesn't exist or isn't imported.
  3. 03Property doesn't exist errors indicate misspelled properties or missing types.

Tips

  1. 01Read the full error message carefully — TypeScript provides helpful suggestions for how to fix the error.

Warnings

  1. 01Avoid using <code>any</code> or <code>as any</code> to silence errors — fix the underlying type issues instead for safer code.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Error Messages
Chapter 05 · Page 36
Beginner

TypeScript Error Messages

(continued)

Type Mismatch Errors

  • Type is not assignable to parameter type.
    function greet(name: string) {
      console.log(`Hello ${name}`);
    }
    
    greet(123); // error: number is not assignable to string
    // Fix: greet("Alice");
  • Property is missing from type.
    interface User {
      name: string;
      age: number;
    }
    
    const user: User = { name: "Alice" };
    // error: property 'age' is missing
    // Fix: add age property
  • Property value has wrong type.
    interface User {
      age: number;
    }
    
    const user: User = { age: "30" };
    // error: string is not assignable to number
    // Fix: change age to 30 (without quotes)
  • Union type is too broad for the required narrow type.
    function setStatus(status: "active" | "inactive") { }
    
    const s: string = "active";
    setStatus(s); // error: string is not assignable
    // Fix: type s as the exact union or use a literal
    setStatus(s as "active" | "inactive");
  • Return type does not match function's declared type.
    function getId(): number {
      return "abc"; // error: string is not assignable to number
    }
    // Fix: return a number
    function getId(): number {
      return 42;
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Error Messages
Chapter 05 · Page 37
Beginner

TypeScript Error Messages

(continued)

Cannot Find Name Errors

  • Variable is not defined in scope.
    console.log(userName);
    // error: Cannot find name 'userName'
    // Fix: declare the variable first
    const userName = "Alice";
    console.log(userName);
  • Import is missing or misspelled.
    const express = require("express");
    app.listen(); // error: cannot find name 'app'
    // Fix: create app instance
    const app = express();
  • Variable is out of scope due to block scoping.
    if (true) {
      const x = 5;
    }
    console.log(x); // error: x is out of scope
    // Fix: move console.log inside the if block or use let/var
  • Type or interface is used but never imported.
    const user: User = { name: "Alice" };
    // error: Cannot find name 'User'
    // Fix: add the import
    import { User } from "./types";
  • Global type not available because lib config is missing.
    // error: Cannot find name 'Promise'
    // Fix: add "es2015" or "es6" to lib in tsconfig.json
    // { "compilerOptions": { "lib": ["es6", "dom"] } }
    const p: Promise<void> = Promise.resolve();
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Error Messages
Chapter 05 · Page 38
Beginner

TypeScript Error Messages

(continued)

Property Does Not Exist Errors

  • Property is misspelled.
    interface User {
      name: string;
    }
    
    const user: User = { name: "Alice" };
    console.log(user.Name); // error: Property 'Name' does not exist
    // Fix: use lowercase 'name'
  • Property exists but type is not declared in the interface.
    const user = { name: "Alice" };
    console.log(user.age); // error: property 'age' does not exist
    // Fix: add age to the type
    interface User { name: string; age: number; }
  • Object might be null or undefined before access.
    const user: User | null = null;
    console.log(user.name); // error: object is possibly null
    // Fix: add null check
    if (user) {
      console.log(user.name);
    }
  • Accessing a property on a union type not shared by all members.
    type Shape = { kind: "circle"; radius: number } | { kind: "square"; side: number };
    
    function area(s: Shape) {
      return s.radius; // error: 'radius' does not exist on 'square'
      // Fix: narrow with a type guard
      if (s.kind === "circle") return Math.PI * s.radius ** 2;
    }
  • Dynamic property key not present in the index signature.
    const map: Record<string, number> = {};
    const val: number = map["key"]; // may be undefined
    // Fix: check first or use optional chaining
    const val2 = map["key"] ?? 0;
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Error Messages
Chapter 05 · Page 39
Beginner

TypeScript Error Messages

(continued)

Function Errors

  • Function arguments don't match parameters.
    function add(a: number, b: number): number {
      return a + b;
    }
    
    add(1); // error: 1 argument provided but 2 required
    // Fix: provide both arguments
    add(1, 2);
  • Return type doesn't match function signature.
    function getName(): string {
      return 123; // error: number is not assignable to string
    }
    // Fix: return a string
    function getName(): string {
      return "Alice";
    }
  • Value is not callable because it is not a function.
    const x = 5;
    x(); // error: This expression is not callable
    // Fix: only call variables that are functions
  • Async function return type is missing the Promise wrapper.
    async function loadUser(): User { // error: missing Promise
      return fetchUser(1);
    }
    // Fix: wrap return type in Promise
    async function loadUser(): Promise<User> {
      return fetchUser(1);
    }
  • Too many arguments passed to a function.
    function greet(name: string) { }
    
    greet("Alice", "extra"); // error: Expected 1 arguments, but got 2
    // Fix: remove extra argument or add a parameter
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Error Messages
Chapter 05 · Page 40
Beginner

TypeScript Error Messages

(continued)

Fixing with Type Assertions

  • Use as to tell TypeScript the actual type.
    const value: unknown = "hello";
    const length = (value as string).length; // OK
  • Use type guards to narrow types safely without assertions.
    const value: string | number = "hello";
    if (typeof value === "string") {
      console.log(value.length); // OK — TypeScript knows it's a string
    }
  • Use non-null assertion (!) only when you are certain the value exists.
    const user: User | null = getUser();
    console.log(user!.name); // tells TypeScript user is not null
    // Only use if you're certain the value is not null
  • Use satisfies to validate a value without widening its type.
    const config = {
      host: "localhost",
      port: 3000
    } satisfies Record<string, string | number>;
    // config.port is still typed as number, not string | number
  • Cast through unknown when TypeScript won't accept a direct assertion.
    const value = getAnything() as unknown as SpecificType;
    // Use sparingly — only when you know the runtime type is correct
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Error Messages
Chapter 05 · Page 41
Beginner

TypeScript Error Messages

(FAQ)

FAQ

This error means the variable or parameter expects a specific type but received a different one — check where the variable is declared and ensure the value you're passing matches that declared type. Often the fix is correcting the data source or updating the type annotation to reflect the actual shape of the data.

The variable is either out of scope, not imported, or not declared before use — add the appropriate import statement or move the declaration above where it's used. For global types like 'process' or 'document', you may need to install the corresponding @types package.

This usually means you're accessing a property that TypeScript doesn't know about — either you've mistyped the property name or the type definition is incomplete. Fix it by correcting the typo, extending the interface with the missing property, or using a type guard to narrow the type before accessing it.

TypeScript checks that function signatures are compatible — if you're passing a callback, ensure the parameter types and count match what the caller expects. When a callback receives more arguments than your function uses, TypeScript is usually fine with that, but missing required parameters will trigger an error.

Type assertions are safe when you have information TypeScript can't infer — such as after a runtime check confirms the shape of parsed JSON or a DOM query result. Avoid asserting to an unrelated type or using it to bypass a legitimate mismatch, as this shifts the type-checking burden entirely to you.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Interfaces
Chapter 06 · Page 42
Beginner

TypeScript Interfaces

Learn how interfaces define object shapes, optional properties, and contracts for cleaner typed code.

TL;DR

  1. 01Describe the shape of objects using interface declarations.
  2. 02Mark properties as optional or readonly when needed.
  3. 03Extend interfaces to reuse and combine type definitions.

Tips

  1. 01Use PascalCase for interface names and skip the old <code>I</code> prefix, since modern TypeScript style avoids it.

Warnings

  1. 01Declaration merging can cause unexpected behavior, so avoid reusing interface names across unrelated files.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Interfaces
Chapter 06 · Page 43
Beginner

TypeScript Interfaces

(continued)

Basic Syntax

  • Declare an interface using the interface keyword and a PascalCase name.
    interface User {
      id: number;
      name: string;
    }
  • Use the interface as a type annotation when declaring variables.
    const user: User = { id: 1, name: "Sam" };
  • List each property with its name and type inside curly braces.
  • Add semicolons or commas between properties — both are valid.
  • Group related properties into one interface to describe a single object shape.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Interfaces
Chapter 06 · Page 44
Beginner

TypeScript Interfaces

(continued)

Property Modifiers

  • Mark a property as optional using a question mark after its name.
    interface User {
      id: number;
      email?: string;
    }
  • Use the readonly keyword to prevent a property from being changed later.
    interface Point {
      readonly x: number;
      readonly y: number;
    }
  • Combine readonly with optional properties for safe configuration objects.
  • Use index signatures for objects with dynamic keys of the same type.
    interface Scores {
      [name: string]: number;
    }
  • Set required properties without modifiers, since required is the default.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Interfaces
Chapter 06 · Page 45
Beginner

TypeScript Interfaces

(continued)

Methods

  • Define methods inside an interface using the shorthand call syntax.
    interface Greeter {
      greet(name: string): string;
    }
  • Add parameter types and return types just like regular function declarations.
  • Use arrow function style with a property and function type.
    interface Greeter {
      greet: (name: string) => string;
    }
  • Both syntax styles work — pick one and stay consistent across your project.
  • Type method overloads by listing multiple call signatures inside the interface.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Interfaces
Chapter 06 · Page 46
Beginner

TypeScript Interfaces

(continued)

Extending

  • Extend an interface using the extends keyword to inherit its properties.
    interface Animal {
      name: string;
    }
    interface Dog extends Animal {
      breed: string;
    }
  • Extend multiple interfaces by listing them separated by commas.
    interface Hybrid extends Animal, Trainable {}
  • Override inherited properties by redeclaring them with a more specific type.
  • Use extension to build small base interfaces into larger composed shapes.
  • Merge two interfaces with the same name automatically through declaration merging.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Interfaces
Chapter 06 · Page 47
Beginner

TypeScript Interfaces

(continued)

Interface vs Type

  • Use interfaces for object shapes and class contracts in most cases.
    interface User { id: number; name: string }
  • Use type aliases for unions, intersections, primitives, and tuples instead.
    type Status = "loading" | "success" | "error";
  • Only interfaces support declaration merging across multiple files.
  • Both support extension, but interfaces use extends and types use intersections.
  • Pick one style per project to keep your codebase consistent.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Interfaces
Chapter 06 · Page 48
Beginner

TypeScript Interfaces

(FAQ)

FAQ

Prefer interfaces when defining object shapes, especially if the type may need to be extended or merged later. Use type aliases for unions, intersections, primitives, or tuples where interfaces can't express the shape.

Mark a property optional with a question mark (e.g., name?: string) and read-only with the readonly keyword (e.g., readonly id: number). These can be combined on the same property.

Yes, an interface can extend multiple interfaces in a single declaration using a comma-separated list: interface C extends A, B {}. This merges all properties from both parent interfaces into the new one.

Declaration merging automatically combines two interfaces with the same name in the same scope into one. This becomes a problem when two unrelated files both define an interface with the same name, silently adding unexpected properties to each other's shapes.

You can define a method either as a property with a function type (greet: (name: string) => void) or using shorthand method syntax (greet(name: string): void). Both are valid, but the shorthand form is more concise and commonly preferred.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Modules
Chapter 07 · Page 49
Beginner

TypeScript Modules

Learn how to organize TypeScript code using ES modules, named exports, and default exports.

TL;DR

  1. 01Use export and import to share code between files.
  2. 02Choose named exports for multiple values per module.
  3. 03Use type-only imports to keep runtime bundles small.

Tips

  1. 01Prefer named exports over default exports, since they keep import names consistent and make refactoring much safer.

Warnings

  1. 01Path aliases require both <code>tsconfig.json</code> and your bundler to agree, or imports will work in the IDE but fail at runtime.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Modules
Chapter 07 · Page 50
Beginner

TypeScript Modules

(continued)

Named Exports

  • Add the export keyword in front of any declaration to share it.
    // utils.ts
    export const PI = 3.14159;
    export function double(n: number) {
      return n * 2;
    }
  • Import named exports using curly braces and the exact names.
    import { PI, double } from "./utils";
  • Rename imports inline with the as keyword to avoid conflicts.
    import { double as multiplyByTwo } from "./utils";
  • Export multiple values from one file for related helpers and constants.
  • This is the most common style in modern TypeScript codebases.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Modules
Chapter 07 · Page 51
Beginner

TypeScript Modules

(continued)

Default Exports

  • Use export default to mark one main export per file.
    // logger.ts
    export default function log(msg: string) {
      console.log(msg);
    }
  • Import the default with any name you choose, without curly braces.
    import log from "./logger";
  • A file can have one default export and any number of named exports.
  • Default exports lose the original name during import, which hurts refactoring.
  • Many style guides recommend named exports over defaults for that reason.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Modules
Chapter 07 · Page 52
Beginner

TypeScript Modules

(continued)

Re-exports

  • Re-export from another file to build a barrel that groups related modules.
    // index.ts
    export { double, PI } from "./utils";
    export { default as log } from "./logger";
  • Use export * from to re-export every named export from a module.
    export * from "./utils";
  • Barrels make imports cleaner across the rest of your codebase.
  • They can also hurt tree-shaking, so use them carefully in large projects.
  • Place barrel files at the root of folders for predictable import paths.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Modules
Chapter 07 · Page 53
Beginner

TypeScript Modules

(continued)

Type-only Imports

  • Use the type keyword to import only types, not runtime values.
    import type { User } from "./types";
  • Type-only imports are erased during compilation for smaller bundles.
  • Mix value and type imports in one statement using the inline type keyword.
    import { type User, getUser } from "./api";
  • Use export type to re-export types without runtime code.
    export type { User } from "./types";
  • This is especially helpful when working with bundlers and isolated modules.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Modules
Chapter 07 · Page 54
Beginner

TypeScript Modules

(continued)

Module Resolution

  • Use relative paths like ./utils for files inside your project.
  • Use bare specifiers like react for packages installed in node_modules.
  • Configure path aliases in tsconfig.json to shorten deep import paths.
    import { Button } from "@/components/Button";
  • The moduleResolution setting controls how the compiler finds modules.
  • Use "bundler" mode in modern projects that rely on tools like Vite or Webpack.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Modules
Chapter 07 · Page 55
Beginner

TypeScript Modules

(FAQ)

FAQ

Named exports let you export multiple values from one file and must be imported using their exact name (or an alias). Default exports allow only one per file and can be imported under any name, which makes them harder to track during refactoring.

Use export * from './module' to re-export all named exports, or export { foo, bar } from './module' to selectively re-export. This is common in index files to create a clean public API for a directory.

Use import type { Foo } from './foo' whenever you only need a type for annotations or generics—TypeScript erases it at compile time, so it never appears in the emitted JavaScript. This keeps bundle size smaller and avoids circular-dependency issues caused by value imports.

TypeScript path aliases in tsconfig.json only affect type-checking; bundlers like webpack, Vite, or esbuild need their own alias configuration to resolve them at runtime. Add matching aliases in your bundler config (e.g., resolve.alias in Vite) to make both environments agree.

You can export both simultaneously: export const Status = { Active: 'active' } as const; export type Status = typeof Status[keyof typeof Status];. Consumers can then import the runtime value and the type from the same path without conflict.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Tsconfig Options
Chapter 08 · Page 56
Beginner

TypeScript Tsconfig Options

Quick reference for the most important tsconfig.json options to configure your TypeScript projects.

TL;DR

  1. 01Use strict mode to catch the most type-related bugs.
  2. 02Set target and module to match your runtime environment.
  3. 03Configure include and exclude to control compiled files.

Tips

  1. 01Start every new project with <code>"strict": true</code>, since adding it later means fixing many type errors at once.

Warnings

  1. 01Changing <code>"target"</code> or <code>"module"</code> mid-project can break your bundler, so update one setting at a time and test.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Tsconfig Options
Chapter 08 · Page 57
Beginner

TypeScript Tsconfig Options

(continued)

Getting Started

  • Generate a starter file by running the compiler with the init flag.
    tsc --init
  • The tsconfig.json file sits at the root of your TypeScript project.
  • All compiler options live under the "compilerOptions" key.
    {
      "compilerOptions": {
        "strict": true
      }
    }
  • Run tsc with no arguments to compile using these settings.
  • Use tsc --noEmit for type-checking only without writing files.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Tsconfig Options
Chapter 08 · Page 58
Beginner

TypeScript Tsconfig Options

(continued)

Strict Mode

  • Enable "strict": true to turn on all strict checks at once.
    { "compilerOptions": { "strict": true } }
  • Strict mode includes noImplicitAny, strictNullChecks, and several others.
  • Use "strictNullChecks" to require explicit handling of null and undefined.
  • Use "noImplicitAny" to flag variables that have no type annotation.
  • Strict mode is the recommended baseline for every new project.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Tsconfig Options
Chapter 08 · Page 59
Beginner

TypeScript Tsconfig Options

(continued)

Target and Module

  • Use "target" to choose the JavaScript version of the compiled output.
    { "compilerOptions": { "target": "ES2022" } }
  • Pick "ES2022" or newer for modern browsers and current Node versions.
  • Use "module" to choose between ESM, CommonJS, or bundler modules.
    { "compilerOptions": { "module": "ESNext" } }
  • Use "lib" to include type definitions for browser APIs or other globals.
  • These settings determine what features your compiled code can use.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Tsconfig Options
Chapter 08 · Page 60
Beginner

TypeScript Tsconfig Options

(continued)

Paths and Includes

  • Use "include" and "exclude" to control which files are compiled.
    {
      "include": ["src/**/*"],
      "exclude": ["node_modules", "dist"]
    }
  • Use "outDir" to choose where compiled JavaScript files are saved.
  • Use "rootDir" to set the root folder of your TypeScript source files.
  • Set up path aliases with "baseUrl" and "paths" for cleaner imports.
    {
      "compilerOptions": {
        "baseUrl": "./src",
        "paths": { "@/*": ["*"] }
      }
    }
  • Path aliases require both tsconfig.json and your bundler to agree.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Tsconfig Options
Chapter 08 · Page 61
Beginner

TypeScript Tsconfig Options

(continued)

Helpful Extras

  • Enable "sourceMap": true to generate source maps for easier debugging.
    { "compilerOptions": { "sourceMap": true } }
  • Use "esModuleInterop": true for cleaner imports from CommonJS modules.
  • Set "skipLibCheck": true to skip type checking of declaration files.
  • Use "resolveJsonModule": true to import JSON files directly in code.
  • Use "jsx": "preserve" for React projects that use a separate bundler.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Tsconfig Options
Chapter 08 · Page 62
Beginner

TypeScript Tsconfig Options

(FAQ)

FAQ

The 'strict' flag enables a group of checks including 'strictNullChecks', 'noImplicitAny', 'strictFunctionTypes', and others. Enabling it together is more reliable than toggling individual flags, since some checks depend on others being active.

Use the 'include' array to specify which directories or globs TypeScript should compile, and 'exclude' to block specific paths like 'node_modules' or 'dist'. Without 'include', TypeScript compiles every .ts file it finds from the project root.

'target' controls the JavaScript syntax version TypeScript outputs (e.g. ES2020), while 'module' controls how imports and exports are emitted (e.g. CommonJS vs ESNext). They are independent—you can target ES5 syntax while still emitting ESNext modules.

Add a 'paths' mapping under 'compilerOptions' and set 'baseUrl' to your project root. Note that TypeScript only handles type resolution—your bundler (Webpack, Vite, etc.) needs its own alias config to resolve these paths at runtime.

TypeScript only uses the nearest tsconfig.json relative to the file being compiled, so nested configs can shadow parent ones. Also, running 'tsc' directly without a file argument reads tsconfig.json, but passing a file explicitly (e.g. 'tsc index.ts') bypasses it entirely.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Type Aliases
Chapter 09 · Page 63
Beginner

TypeScript Type Aliases

Understand type aliases, unions, and intersections to make your TypeScript code more expressive.

TL;DR

  1. 01Create reusable type names with the type keyword.
  2. 02Combine types using unions and intersections for flexibility.
  3. 03Lock values to literals for precise type safety.

Tips

  1. 01Use type aliases for unions and intersections, and use interfaces for object shapes you may want to extend later.

Warnings

  1. 01Type aliases cannot be merged like interfaces, so duplicate names will cause a compiler error.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Type Aliases
Chapter 09 · Page 64
Beginner

TypeScript Type Aliases

(continued)

Basic Syntax

  • Declare a type alias using the type keyword and PascalCase naming.
    type User = {
      id: number;
      name: string;
    };
  • Assign any valid type, including objects, primitives, or other type aliases.
    type ID = number | string;
  • Use the alias anywhere you would normally write the full type expression.
    const user: User = { id: 1, name: "Sam" };
  • Type aliases can describe object shapes much like interfaces can.
  • Keep aliases short and focused to make them easy to reuse.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Type Aliases
Chapter 09 · Page 65
Beginner

TypeScript Type Aliases

(continued)

Union Types

  • Combine types with the pipe symbol for multiple allowed options.
    type ID = string | number;
    const a: ID = "abc";
    const b: ID = 42;
  • Use unions to allow a variable to hold values of different shapes.
  • Narrow union types inside code using typeof or in checks.
    function format(value: string | number) {
      if (typeof value === "string") return value.trim();
      return value.toFixed(2);
    }
  • Build status types from string literals for clear, restricted state.
    type Status = "loading" | "success" | "error";
  • Pass unions as function parameters to accept several valid input types.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Type Aliases
Chapter 09 · Page 66
Beginner

TypeScript Type Aliases

(continued)

Intersection Types

  • Combine multiple types into one using the ampersand symbol between them.
    type Named = { name: string };
    type Aged = { age: number };
    type Person = Named & Aged;
  • Intersections merge all properties from each type into a single shape.
    const sam: Person = { name: "Sam", age: 30 };
  • Use intersections to compose small types into larger object definitions.
  • Conflicting property types in intersections create unusable never values.
  • Prefer intersections over interface extension when working with type aliases.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Type Aliases
Chapter 09 · Page 67
Beginner

TypeScript Type Aliases

(continued)

Literal Types

  • Restrict a value to one exact string, number, or boolean literal.
    type Direction = "up" | "down" | "left" | "right";
  • Combine literal types with unions to create fixed sets of allowed values.
    type Size = "sm" | "md" | "lg";
  • Use literals for things like API methods, sizes, or status codes.
  • Add as const to objects to make all properties literal and readonly.
    const CONFIG = { mode: "prod" } as const;
  • Literal types catch typos that regular string types would silently allow.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Type Aliases
Chapter 09 · Page 68
Beginner

TypeScript Type Aliases

(continued)

Advanced Patterns

  • Use generic type aliases for flexible, reusable type shapes.
    type Box<T> = { value: T };
    const n: Box<number> = { value: 42 };
  • Build conditional types with the ternary-style syntax for advanced logic.
    type IsString<T> = T extends string ? true : false;
  • Use mapped types to transform every key in another type.
    type Optional<T> = { [K in keyof T]?: T[K] };
  • Combine aliases with utility types to build reusable transformation patterns.
  • Export type aliases from a shared file to keep types organized.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Type Aliases
Chapter 09 · Page 69
Beginner

TypeScript Type Aliases

(FAQ)

FAQ

Use a type alias when you need unions, intersections, or primitive types — things interfaces can't express. Prefer interfaces for object shapes that other types might extend or implement, since interfaces support declaration merging and are more idiomatic for OOP patterns.

You can't use the extends keyword directly on a type alias, but you can compose them using intersection types: type Admin = User & { role: 'admin' }. For true inheritance hierarchies, interfaces are more ergonomic since they natively support extends.

Declare it with the type keyword and separate each member with a pipe: type Status = 'active' | 'inactive' | 'pending'. You can union primitives, literals, or other type aliases together in a single declaration.

Unlike interfaces, type aliases do not support declaration merging — defining the same type name twice is a compiler error. Rename one of them or consolidate the definitions into a single type alias.

A union type (A | B) means a value can be either A or B, while an intersection type (A & B) means the value must satisfy both A and B simultaneously. Intersections are commonly used to merge object shapes, whereas unions model values that can take one of several forms.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Async Patterns
Chapter 10 · Page 70
Intermediate

TypeScript Async Patterns

A quick reference for async/await, Promises, error handling, and concurrent operations in TypeScript.

TL;DR

  1. 01Use async/await to write asynchronous code that reads synchronously.
  2. 02Always wrap async calls in try/catch to handle Promise rejections.
  3. 03Use Promise.all to run independent async operations in parallel.

Tips

  1. 01Use Promise.all for independent operations and sequential awaits for dependent ones — it makes a huge performance difference.

Warnings

  1. 01Always handle errors in async functions — unhandled rejections can crash your application.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Async Patterns
Chapter 10 · Page 71
Intermediate

TypeScript Async Patterns

(continued)

Async/Await Basics

  • Use async/await for readable asynchronous code.
    async function fetchUser(id: number): Promise<User> {
      const response = await fetch(`/api/users/${id}`);
      return response.json();
    }
  • Await pauses execution until the Promise resolves.
    async function loadData() {
      const data = await fetchUser(1); // waits here
      console.log(data.name);         // runs after fetch completes
    }
  • Async functions always return a Promise automatically.
    async function getValue(): Promise<number> {
      return 42; // wrapped in Promise automatically
    }
  • Chain multiple awaits for sequential dependent calls.
    async function loadProfile(userId: number) {
      const user = await fetchUser(userId);
      const posts = await fetchPosts(user.id);
      return { user, posts };
    }
  • Mark top-level module code as async when needed.
    // top-level await in modules
    const config = await loadConfig();
    console.log(config.apiUrl);
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Async Patterns
Chapter 10 · Page 72
Intermediate

TypeScript Async Patterns

(continued)

Error Handling

  • Handle errors with try/catch blocks.
    async function fetchData() {
      try {
        const data = await fetch('/api/data');
        return data.json();
      } catch (error) {
        console.error('Failed to fetch:', error);
        return null;
      }
    }
  • Catch both fetch and JSON parsing errors in one block.
    async function safeLoad(url: string) {
      try {
        const res = await fetch(url);
        const json = await res.json(); // can also throw
        return json;
      } catch (err) {
        return null;
      }
    }
  • Use a typed catch block to inspect the error shape.
    async function fetchUser(id: number) {
      try {
        return await getUser(id);
      } catch (err) {
        if (err instanceof Error) {
          console.error(err.message);
        }
      }
    }
  • Re-throw after logging to propagate to the caller.
    async function loadConfig() {
      try {
        return await fetchConfig();
      } catch (err) {
        logger.error('Config load failed', err);
        throw err; // let the caller decide
      }
    }
  • Handle HTTP errors separately from network errors.
    async function apiGet(url: string) {
      const res = await fetch(url);
      if (!res.ok) {
        throw new Error(`HTTP ${res.status}: ${res.statusText}`);
      }
      return res.json();
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Async Patterns
Chapter 10 · Page 73
Intermediate

TypeScript Async Patterns

(continued)

Concurrent Operations

  • Run multiple async operations in parallel with Promise.all.
    async function loadAllData() {
      const [users, posts, comments] = await Promise.all([
        fetchUsers(),
        fetchPosts(),
        fetchComments()
      ]);
      return { users, posts, comments };
    }
  • Promise.all is faster than sequential awaits for independent calls.
    // Slow: sequential (waits each time)
    const user = await fetchUser(1);
    const posts = await fetchPosts(1);
    
    // Fast: parallel (runs at same time)
    const [user, posts] = await Promise.all([fetchUser(1), fetchPosts(1)]);
  • Use Promise.allSettled to collect all results even on failure.
    const results = await Promise.allSettled([
      fetchUser(1),
      fetchUser(2),
      fetchUser(3)
    ]);
    
    results.forEach(r => {
      if (r.status === "fulfilled") console.log(r.value);
      else console.error(r.reason);
    });
  • Use Promise.race to get the first resolved result.
    const fastest = await Promise.race([
      fetchFromRegion("us"),
      fetchFromRegion("eu")
    ]);
  • Use Promise.any to get first fulfilled (ignores rejections).
    const first = await Promise.any([
      fetchMirror1(),
      fetchMirror2(),
      fetchMirror3()
    ]);
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Async Patterns
Chapter 10 · Page 74
Intermediate

TypeScript Async Patterns

(continued)

Sequential Operations

  • Run operations in sequence when the result depends on the previous call.
    async function getPostWithAuthor(postId: number) {
      const post = await fetchPost(postId);
      const author = await fetchUser(post.userId);
      return { post, author };
    }
  • Await each operation in order to preserve dependency.
    async function createAndNotify(data: NewUser) {
      const user = await createUser(data);
      const token = await generateToken(user.id);
      await sendWelcomeEmail(user.email, token);
      return user;
    }
  • Loop with await to process items one at a time.
    async function processQueue(ids: number[]) {
      for (const id of ids) {
        await processItem(id); // each waits before next
      }
    }
  • Use reduce to accumulate sequential async results.
    async function runSteps(steps: Array<() => Promise<string>>) {
      return steps.reduce(async (prev, step) => {
        const results = await prev;
        const result = await step();
        return [...results, result];
      }, Promise.resolve<string[]>([]));
    }
  • Use for-await-of to iterate async iterables.
    async function readStream(stream: AsyncIterable<string>) {
      for await (const chunk of stream) {
        console.log(chunk);
      }
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Async Patterns
Chapter 10 · Page 75
Intermediate

TypeScript Async Patterns

(continued)

Type-Safe Async Operations

  • Type Promise return values explicitly for caller safety.
    async function getData(): Promise<{ id: number; name: string }> {
      const response = await fetch('/api/data');
      return response.json();
    }
  • Use generics to build reusable typed fetch helpers.
    async function fetchData<T>(url: string): Promise<T> {
      const response = await fetch(url);
      return response.json() as T;
    }
    
    const user = await fetchData<User>('/api/user/1');
  • Type async callbacks passed as arguments.
    async function withRetry<T>(
      fn: () => Promise<T>,
      retries: number
    ): Promise<T> {
      for (let i = 0; i <= retries; i++) {
        try { return await fn(); } catch (e) {
          if (i === retries) throw e;
        }
      }
      throw new Error("unreachable");
    }
  • Use Result types to encode success and failure explicitly.
    type Result<T> = { ok: true; value: T } | { ok: false; error: string };
    
    async function safeFetch(url: string): Promise<Result<unknown>> {
      try {
        const data = await (await fetch(url)).json();
        return { ok: true, value: data };
      } catch (e) {
        return { ok: false, error: String(e) };
      }
    }
  • Annotate async generator functions with their yield types.
    async function* paginate<T>(
      load: (page: number) => Promise<T[]>
    ): AsyncGenerator<T> {
      let page = 0;
      while (true) {
        const items = await load(page++);
        if (!items.length) break;
        for (const item of items) yield item;
      }
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Async Patterns
Chapter 10 · Page 76
Intermediate

TypeScript Async Patterns

(FAQ)

FAQ

Annotate the return type as Promise, for example async function fetchUser(): Promise. TypeScript will enforce that all resolved values match T and flag type mismatches at compile time.

Sequential awaits run one after another, so total time is the sum of each duration. Promise.all launches all operations simultaneously and resolves when the last one finishes, which is far faster when the operations don't depend on each other's results.

It does catch them — but only if you await Promise.all inside the try block. If you store the Promise without awaiting it, the rejection goes unhandled. Always use await Promise.all([...]) directly inside try/catch.

TypeScript types caught errors as unknown, so narrow the type before using it: if (error instanceof Error) { console.log(error.message) }. Avoid casting to any, as that defeats type safety.

Technically yes, but the loop won't wait for each async call to finish — forEach doesn't await the returned Promises. Use a for...of loop with await instead, or Promise.all with .map() if the iterations are independent.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Declaration Files
Chapter 11 · Page 77
Intermediate

TypeScript Declaration Files

Write .d.ts files, publish typed npm packages, and create type stubs.

TL;DR

  1. 01Create .d.ts files to provide types for JavaScript libraries.
  2. 02Use declare keyword to define types without implementation.
  3. 03Publish types alongside your npm package for TypeScript users.

Tips

  1. 01Always include declaration files with your npm package or publish to DefinitelyTyped — TypeScript users will appreciate the type safety.

Warnings

  1. 01Keep declaration files accurate and in sync with implementation — incorrect types are worse than no types at all.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Declaration Files
Chapter 11 · Page 78
Intermediate

TypeScript Declaration Files

(continued)

Writing Declaration Files

  • Create .d.ts files to define types for JavaScript code.
    // mylib.d.ts
    export function greet(name: string): string;
    export interface User {
      id: number;
      name: string;
    }
  • Declaration files contain only type definitions, no implementation.
    // index.d.ts
    export class Calculator {
      add(a: number, b: number): number;
      subtract(a: number, b: number): number;
    }
  • Use declare to define types for existing JavaScript globals.
    declare global {
      interface Window {
        myGlobal: string;
      }
    }
  • Declare ambient variables that exist at runtime but not in source.
    declare const __VERSION__: string;
    declare function require(module: string): any;
  • Use declare module for UMD or non-ES library entry points.
    export as namespace MyLib;
    export function init(options: Options): void;
    export interface Options {
      debug?: boolean;
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Declaration Files
Chapter 11 · Page 79
Intermediate

TypeScript Declaration Files

(continued)

Module Declaration

  • Declare types for entire modules or namespaces.
    declare module "my-library" {
      export function process(data: any): any;
      export const version: string;
    }
  • Augment existing modules with additional types.
    declare module "express" {
      interface Request {
        userId?: number;
      }
    }
  • Use triple-slash directives to reference other declaration files.
    /// <reference path="./types.d.ts" />
    /// <reference types="node" />
  • Organize types with namespaces for large libraries.
    declare namespace MyLib {
      interface Config { }
      function init(config: Config): void;
    }
  • Use a wildcard module declaration for file imports like CSS or SVG.
    declare module "*.svg" {
      const content: string;
      export default content;
    }
    
    declare module "*.css" {
      const styles: Record<string, string>;
      export default styles;
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Declaration Files
Chapter 11 · Page 80
Intermediate

TypeScript Declaration Files

(continued)

Package Configuration

  • Point to declaration files in package.json with the types field.
    {
      "name": "my-library",
      "version": "1.0.0",
      "main": "dist/index.js",
      "types": "dist/index.d.ts"
    }
  • Emit declaration files from TypeScript during build.
    {
      "compilerOptions": {
        "declaration": true,
        "declarationDir": "./dist",
        "outDir": "./dist"
      }
    }
  • Use typesVersions to support different TypeScript versions.
    {
      "typesVersions": {
        "<=4.0": { "*": ["ts4.0/*"] },
        ">=4.1": { "*": ["*"] }
      }
    }
  • Use exports map with types conditions for dual CJS/ESM packages.
    {
      "exports": {
        ".": {
          "import": { "types": "./dist/esm/index.d.ts", "default": "./dist/esm/index.js" },
          "require": { "types": "./dist/cjs/index.d.ts", "default": "./dist/cjs/index.js" }
        }
      }
    }
  • Generate declaration maps for go-to-definition in source.
    {
      "compilerOptions": {
        "declarationMap": true,
        "declaration": true,
        "sourceRoot": "./src"
      }
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Declaration Files
Chapter 11 · Page 81
Intermediate

TypeScript Declaration Files

(continued)

Publishing to DefinitelyTyped

  • Contribute type definitions for popular untyped libraries.
    DefinitelyTyped/types/
      library-name/
        index.d.ts
        package.json
        tsconfig.json
        tests.ts
  • Test declaration files with sample code before submitting.
    // tests.ts
    import * as lib from "./index";
    
    const result: string = lib.process("test");
    const version: string = lib.version;
  • Include a package.json in the types folder with the correct shape.
    {
      "name": "@types/library-name",
      "version": "1.0.0",
      "typings": "index.d.ts"
    }
  • Add a tsconfig.json to validate declarations during CI.
    {
      "compilerOptions": {
        "module": "commonjs",
        "lib": ["es6"],
        "noImplicitAny": true,
        "noImplicitThis": true,
        "strictNullChecks": true,
        "strictFunctionTypes": true
      }
    }
  • Run dtslint to catch type errors before opening a pull request.
    npm install -g dtslint
    dtslint types/library-name
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Declaration Files
Chapter 11 · Page 82
Intermediate

TypeScript Declaration Files

(continued)

Best Practices

  • Keep declaration files in sync with implementation.
    // Don't declare types that don't match the implementation
    export function add(a: number, b: number): number;
    // Should match: const add = (a, b) => a + b;
  • Export only public APIs and hide internal helpers.
    // Correct: public types are exported
    export interface PublicAPI { }
    
    // Wrong: internal types shouldn't be exported
    // export interface _InternalHelper { }
  • Include JSDoc comments for better IDE support.
    /**
     * Processes data asynchronously
     * @param data - The input data to process
     * @returns A promise that resolves with the result
     */
    export function processAsync(data: any): Promise<any>;
  • Use semver versioning for types to avoid breaking changes.
    # Bump patch for additions, minor for new overloads, major for removals
    npm version patch  # safe: new optional param
    npm version major  # breaking: removed export
  • Avoid using any in declaration files — prefer unknown or generics.
    // Weak: loses type information
    export function parse(input: string): any;
    
    // Better: let the caller specify the return type
    export function parse<T>(input: string): T;
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Declaration Files
Chapter 11 · Page 83
Intermediate

TypeScript Declaration Files

(FAQ)

FAQ

A .d.ts file contains only type declarations with no runtime code — it uses the declare keyword to describe the shape of existing JavaScript without any implementation. Regular .ts files compile to JavaScript, while declaration files are consumed only by the TypeScript compiler for type checking.

Add a "types" or "typings" field in your package.json pointing to your main .d.ts file, e.g. "types": "./dist/index.d.ts". If your types file has the same name as your "main" entry but with a .d.ts extension, TypeScript will find it automatically without the explicit field.

Publish to DefinitelyTyped (@types/yourpackage) when you're adding types for a third-party JavaScript library you don't own or maintain. If you own the package, bundle the .d.ts files directly — it keeps types versioned in sync with the implementation and simplifies the install process for consumers.

Use export = myFunction syntax in your declaration file instead of export default, since export default doesn't match how CommonJS modules work at runtime. Consumers using import myFn from 'pkg' will need esModuleInterop: true in their tsconfig to use export = style declarations.

Yes — set "declaration": true in your tsconfig.json and TypeScript will emit .d.ts files alongside your compiled output automatically. This is the recommended approach for TypeScript-authored libraries; hand-written declarations are only necessary when typing an existing JavaScript codebase.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Generics
Chapter 12 · Page 84
Intermediate

TypeScript Generics

Master generics to build reusable, type-safe functions, classes, and components in TypeScript.

TL;DR

  1. 01Use type parameters to write reusable, type-safe code.
  2. 02Constrain generics to restrict what types they accept.
  3. 03Provide default types to simplify common generic usage.

Tips

  1. 01Name generics meaningfully, like <code>TItem</code> or <code>TResponse</code>, when single letters make the code harder to read.

Warnings

  1. 01Avoid overusing generics in simple functions, since they can make code harder to read and maintain.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Generics
Chapter 12 · Page 85
Intermediate

TypeScript Generics

(continued)

Basics

  • Add a type parameter in angle brackets to make a function generic.
    function identity<T>(value: T): T {
      return value;
    }
  • The letter T is a placeholder that gets replaced with a real type.
  • TypeScript infers the type from the argument you pass to the function.
    identity("hello"); // T inferred as string
    identity(42);      // T inferred as number
  • You can also pass the type explicitly using angle brackets at the call.
    identity<string>("hello");
  • Use single uppercase letters or short descriptive names for type parameters.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Generics
Chapter 12 · Page 86
Intermediate

TypeScript Generics

(continued)

Generic Functions

  • Write a generic function once and reuse it for many different types.
    function first<T>(items: T[]): T | undefined {
      return items[0];
    }
  • Return values keep their original type, unlike using any for parameters.
  • Pass arrays generically with T[] or Array<T> for typed list operations.
  • Use multiple type parameters when handling pairs of types.
    function pair<T, U>(a: T, b: U): [T, U] {
      return [a, b];
    }
  • Chain generics through helper functions to keep type information intact.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Generics
Chapter 12 · Page 87
Intermediate

TypeScript Generics

(continued)

Constraints

  • Limit a type parameter using extends to require certain properties.
    function byId<T extends { id: number }>(items: T[], id: number) {
      return items.find(i => i.id === id);
    }
  • Constraints make sure the type has the properties your function needs.
  • Use keyof T to constrain a parameter to keys of another type.
    function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
      return obj[key];
    }
  • Combine constraints with conditional types for advanced type narrowing.
  • Constraints still allow types that include extra properties beyond the requirement.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Generics
Chapter 12 · Page 88
Intermediate

TypeScript Generics

(continued)

Default Types

  • Set a default type to simplify common use cases for callers.
    interface ApiResponse<T = unknown> {
      data: T;
      status: number;
    }
  • Default types let callers skip the type argument when the default fits.
    const res: ApiResponse = { data: null, status: 200 };
  • Combine defaults with constraints to keep types both safe and easy.
    type Config<T extends object = {}> = { values: T };
  • Use defaults to keep public APIs clean while supporting custom overrides.
  • Defaults are especially useful for generic React components and utility types.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Generics
Chapter 12 · Page 89
Intermediate

TypeScript Generics

(continued)

Generic Classes

  • Add type parameters to class names for typed instances and methods.
    class Box<T> {
      constructor(public value: T) {}
      get() { return this.value; }
    }
  • Use the type parameter inside properties, methods, and constructor signatures.
  • Pass the type when creating an instance, or let it be inferred.
    const a = new Box<string>("hello");
    const b = new Box(42); // inferred as Box<number>
  • Generic classes work well for collections, queues, stacks, and state stores.
  • TypeScript will often infer the type from the constructor argument automatically.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Generics
Chapter 12 · Page 90
Intermediate

TypeScript Generics

(FAQ)

FAQ

Use the extends keyword to restrict what types are accepted, e.g., function getLength<T extends { length: number }>(arg: T): number. This lets TypeScript guarantee properties exist on the type parameter without widening to any.

Use generics when the relationship between input and output types matters — function first<T>(arr: T[]): T preserves that the return type matches the array element type, which any would lose. Union types are better when a fixed set of known types is acceptable and reusability across unknown types isn't needed.

TypeScript infers type parameters from call-site arguments in most cases, so wrap(42) on function wrap<T>(val: T): T[] resolves T as number without annotation. Explicit type arguments like wrap<number>(42) are only needed when inference fails or you want to enforce a stricter type than what would be inferred.

Assign a default with =, like type Result<T = string> = { data: T; error: string | null }, so callers can omit the type argument when the default covers their case. This is particularly useful for generic utility types where one type dominates most usage but the generic remains available for edge cases.

Declare the type parameter on the class itself — class Repository<T> { findById(id: number): T {} } — so every method and property in the class operates on the same T. This differs from a generic method, where T is only in scope for that single method call.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Generics Constraints
Chapter 13 · Page 91
Intermediate

TypeScript Generics Constraints

Build flexible but safe types using constrained generics.

TL;DR

  1. 01Use extends to constrain generic types to specific shapes.
  2. 02Use keyof to safely access object properties.
  3. 03Combine constraints with conditional types for powerful patterns.

Tips

  1. 01Use constraints to catch errors at compile time instead of runtime — this prevents many subtle bugs in generic code.

Warnings

  1. 01Over-constraining generics can make types too restrictive — find the right balance between flexibility and safety.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Generics Constraints
Chapter 13 · Page 92
Intermediate

TypeScript Generics Constraints

(continued)

Basic Constraints

  • Constrain generic type to a specific type with extends.
    function getLength<T extends { length: number }>(item: T): number {
      return item.length;
    }
    
    getLength("hello");     // works: string has length
    getLength([1, 2, 3]);   // works: array has length
    getLength(123);         // error: number has no length
  • Constrain to primitive types.
    function identity<T extends string | number>(value: T): T {
      return value;
    }
    
    identity("text");  // works
    identity(42);      // works
    identity({});      // error
  • Extend specific classes or interfaces.
    interface HasId {
      id: string;
    }
    
    function getId<T extends HasId>(item: T): string {
      return item.id;
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Generics Constraints
Chapter 13 · Page 93
Intermediate

TypeScript Generics Constraints

(continued)

Keyof Constraints

  • Use keyof to safely access object properties.
    function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
      return obj[key];
    }
    
    const user = { name: "Alice", age: 30 };
    getProperty(user, "name"); // works: "name" is a key
    getProperty(user, "email"); // error: "email" is not a key
  • Create flexible getters with key constraints.
    function getProp<T extends object, K extends keyof T>(
      obj: T,
      key: K
    ): T[K] {
      return obj[key];
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Generics Constraints
Chapter 13 · Page 94
Intermediate

TypeScript Generics Constraints

(continued)

Multiple Constraints

  • Combine constraints with intersection types.
    interface HasId {
      id: string;
    }
    
    interface HasName {
      name: string;
    }
    
    function process<T extends HasId & HasName>(item: T): string {
      return `${item.id}: ${item.name}`;
    }
  • Constrain multiple generic parameters.
    function merge<T extends object, U extends object>(
      obj1: T,
      obj2: U
    ): T & U {
      return { ...obj1, ...obj2 };
    }
  • Use conditional constraints for complex logic.
    function getValue<T extends string | number | boolean>(
      value: T
    ): T extends string ? string : T extends number ? number : boolean {
      return value;
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Generics Constraints
Chapter 13 · Page 95
Intermediate

TypeScript Generics Constraints

(continued)

Constructor Constraints

  • Constrain generic to a class constructor.
    function create<T extends { new (...args: any[]): {} }>(
      constructor: T
    ): InstanceType<T> {
      return new constructor();
    }
    
    class User { }
    const user = create(User); // user is typed as User
  • Accept class instances with type preservation.
    function instantiate<T extends new () => any>(
      Class: T
    ): InstanceType<T> {
      return new Class();
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Generics Constraints
Chapter 13 · Page 96
Intermediate

TypeScript Generics Constraints

(continued)

Practical Patterns

  • Create type-safe array utilities with constraints.
    function findById<T extends { id: string | number }>(
      items: T[],
      id: string | number
    ): T | undefined {
      return items.find(item => item.id === id);
    }
  • Build flexible mappers with key constraints.
    function mapProperty<T, K extends keyof T>(
      items: T[],
      key: K
    ): T[K][] {
      return items.map(item => item[key]);
    }
    
    const names = mapProperty(users, "name");
  • Create type-safe event handlers.
    type EventMap = {
      login: { userId: string };
      logout: { timestamp: number };
    };
    
    function on<E extends keyof EventMap>(
      event: E,
      handler: (data: EventMap[E]) => void
    ) {
      // Type-safe handler
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Generics Constraints
Chapter 13 · Page 97
Intermediate

TypeScript Generics Constraints

(FAQ)

FAQ

Use extends with an object shape: function getLength<T extends { length: number }>(arg: T) ensures T always has a length property. This catches type errors at compile time rather than failing at runtime.

keyof T produces a union of all keys of T, while T extends keyof U constrains T to be one of U's keys. Use the latter to safely index into objects: function get<T, K extends keyof T>(obj: T, key: K): T[K].

Yes, use an intersection type: function process<T extends Serializable & Identifiable>(item: T) requires T to satisfy both shapes simultaneously. This is more flexible than creating a combined interface when you don't control the types.

Constrain with new(): function create<T>(ctor: new () => T): T { return new ctor(); }. For constructors with arguments, use new (...args: any[]) => T to accept any constructor signature.

Constraints only guarantee the minimum shape — TypeScript still won't let you access properties not declared in the constraint, even if they exist at runtime. Narrow the constraint to include every property you access, or use a type assertion as a last resort inside the function body.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Null Safety
Chapter 14 · Page 98
Intermediate

TypeScript Null Safety

A reference for handling null and undefined safely in TypeScript projects.

TL;DR

  1. 01Use optional chaining (?.) to safely access nested properties on nullable values.
  2. 02Use nullish coalescing (??) to supply default values for null or undefined.
  3. 03Apply type guards to narrow a union type before accessing its properties.

Tips

  1. 01Enable strictNullChecks in tsconfig.json to catch null safety issues at compile time.

Warnings

  1. 01Non-null assertions (!) bypass type checking — use them only when you're certain the value is not null.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Null Safety
Chapter 14 · Page 99
Intermediate

TypeScript Null Safety

(continued)

Optional Chaining

  • Safely access nested properties with optional chaining.
    const user: User | null = getUser();
    
    // Without optional chaining (error if user is null)
    const name = user.profile.name; // Error
    
    // With optional chaining (safe)
    const name = user?.profile?.name; // undefined if user is null
  • Optional chaining returns undefined if any part is null or undefined.
    const city = user?.address?.city; // undefined rather than throwing
  • Chain optional method calls with ?. before the parentheses.
    const length = user?.getName?.(); // undefined if getName doesn't exist
  • Combine optional chaining with nullish coalescing for fallback values.
    const displayName = user?.profile?.displayName ?? "Anonymous";
  • Use optional chaining on array access and computed properties.
    const firstTag = post?.tags?.[0];          // undefined if tags is absent
    const value = map?.["dynamic-key"]?.trim(); // safe dynamic key access
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Null Safety
Chapter 14 · Page 100
Intermediate

TypeScript Null Safety

(continued)

Nullish Coalescing

  • Use ?? to provide default values only for null or undefined.
    const name = user?.name ?? "Guest";
    const count = value ?? 0;
  • Unlike ||, ?? does not replace empty strings or zero.
    const count = 0;
    
    console.log(count || 10); // 10 — wrong, zero is falsy
    console.log(count ?? 10); // 0  — correct, zero is not null
  • Use ??= to assign a default only when the variable is null or undefined.
    let config: Config | null = null;
    config ??= defaultConfig; // assigned only when null or undefined
  • Chain ?? with optional chaining for safe nested default values.
    const role = user?.permissions?.role ?? "viewer";
  • Use ?? in function parameters to handle missing arguments.
    function paginate(page: number | null, size: number | null) {
      const p = page ?? 1;
      const s = size ?? 20;
      return { page: p, size: s };
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Null Safety
Chapter 14 · Page 101
Intermediate

TypeScript Null Safety

(continued)

Type Guards

  • Check for null before accessing properties.
    function printLength(value: string | null) {
      if (value !== null) {
        console.log(value.length); // Safe: value is string
      }
    }
  • Use typeof to guard primitive types.
    if (typeof value === "string") {
      console.log(value.toUpperCase()); // Safe: narrowed to string
    }
  • Use instanceof to narrow class instances.
    function handle(err: unknown) {
      if (err instanceof Error) {
        console.error(err.message); // Safe: narrowed to Error
      }
    }
  • Write a custom type guard function with a type predicate.
    function isUser(value: unknown): value is User {
      return typeof value === "object" && value !== null && "name" in value;
    }
    
    if (isUser(data)) {
      console.log(data.name); // Safe: narrowed to User
    }
  • Use in operator to narrow discriminated union types.
    type Cat = { meow(): void };
    type Dog = { bark(): void };
    
    function speak(animal: Cat | Dog) {
      if ("meow" in animal) animal.meow();
      else animal.bark();
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Null Safety
Chapter 14 · Page 102
Intermediate

TypeScript Null Safety

(continued)

Optional Types

  • Mark properties as optional with ? to allow undefined.
    interface User {
      name: string;
      email?: string;       // can be undefined
      phone: string | null; // can be null (must be explicit)
    }
  • Optional properties don't need to be included when constructing an object.
    const user: User = {
      name: "Alice"
      // email is optional — safe to omit
    };
  • Distinguish optional (?) from nullable (| null) for precise types.
    interface Post {
      title: string;
      subtitle?: string;       // missing or undefined
      deletedAt: Date | null;  // always present, but can be null
    }
  • Mark function parameters optional to make them skippable.
    function greet(name: string, title?: string): string {
      return title ? `Hello, ${title} ${name}` : `Hello, ${name}`;
    }
    
    greet("Alice");           // OK
    greet("Alice", "Dr.");    // OK
  • Use Required to remove optional modifiers when needed.
    interface Options {
      timeout?: number;
      retries?: number;
    }
    
    function runWithDefaults(opts: Required<Options>) {
      console.log(opts.timeout, opts.retries); // both guaranteed present
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Null Safety
Chapter 14 · Page 103
Intermediate

TypeScript Null Safety

(continued)

Non-Null Assertion

  • Use ! to tell TypeScript a value is not null or undefined.
    const value = getValue();
    const length = value!.length; // Assert value is not null
  • Prefer an explicit null check over the non-null assertion.
    // Good: check first — safe at runtime
    if (value !== null) {
      console.log(value.length);
    }
    
    // Avoid: assertion without check — can throw at runtime
    console.log(value!.length);
  • Use ! on DOM queries when you know the element exists.
    const input = document.getElementById("email")!;
    // Safe only if the element is guaranteed in the HTML
  • Avoid ! inside library code — callers may not share your assumptions.
    // Better: surface the possibility of null in the return type
    function findUser(id: number): User | null {
      return db.users.find(u => u.id === id) ?? null;
    }
  • Use optional chaining as a safer alternative to non-null assertion.
    // Assertion — crashes if null
    element!.classList.add("active");
    
    // Optional chaining — silently skips if null
    element?.classList.add("active");
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Null Safety
Chapter 14 · Page 104
Intermediate

TypeScript Null Safety

(FAQ)

FAQ

Optional chaining (?.) short-circuits to undefined when it encounters null or undefined at any point in the chain, while && short-circuits to the falsy value itself (including 0 or empty string). Prefer ?. when you only care about null/undefined, and && when you need to guard against all falsy values.

Use ?? when your fallback should only trigger for null or undefined, not for other falsy values like 0, false, or empty string. For example, count ?? 0 keeps 0 as a valid value, whereas count || 0 would incorrectly replace it.

Use a type guard such as if (value !== null && value !== undefined) or if (value) before accessing properties — TypeScript will narrow the type inside the block. You can also write a custom type guard function with a value is Type return annotation for reusable narrowing logic.

An optional property (name?: string) can be omitted from the object entirely and has an implicit undefined, while an explicit union (name: string | null) requires the property to be present but allows null as a value. Choose optional for properties that may not exist, and string | null when the property must always be declared but can have no value.

This usually happens when the value is accessed outside the narrowing block, reassigned between the check and use, or when TypeScript can't track the control flow — for instance, after an async gap or inside a callback. Move the property access inside the guard block, or assign the narrowed value to a const before the async operation.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Strict Mode
Chapter 15 · Page 105
Intermediate

TypeScript Strict Mode

Understand strict mode benefits, gotchas, and how to enable it progressively.

TL;DR

  1. 01Enable "strict": true in tsconfig.json for maximum type safety.
  2. 02Strict mode includes multiple flags that can be enabled individually.
  3. 03Migrate existing code gradually by enabling flags one at a time.

Tips

  1. 01Enable strict mode from the start on new projects — fixing types upfront is easier than retrofitting them later.

Warnings

  1. 01Enabling strict mode on large existing codebases requires significant effort — plan for gradual migration over multiple sprints.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Strict Mode
Chapter 15 · Page 106
Intermediate

TypeScript Strict Mode

(continued)

Enabling Strict Mode

  • Enable strict mode to enable all strict flags at once.
    {
      "compilerOptions": {
        "strict": true
      }
    }
  • Strict mode enables these flags simultaneously.
    {
      "compilerOptions": {
        "noImplicitAny": true,
        "noImplicitThis": true,
        "strictNullChecks": true,
        "strictFunctionTypes": true,
        "strictBindCallApply": true,
        "strictPropertyInitialization": true,
        "noImplicitReturns": true,
        "noFallthroughCasesInSwitch": true
      }
    }
  • New TypeScript projects should use strict mode from the start.
    {
      "compilerOptions": {
        "strict": true,
        "target": "ES2020",
        "module": "ESNext"
      }
    }
  • Existing projects can migrate gradually by enabling individual flags.
    {
      "compilerOptions": {
        "strict": false,
        "noImplicitAny": true
      }
    }
  • Use noEmitOnError to prevent emitting code when type errors exist.
    {
      "compilerOptions": {
        "strict": true,
        "noEmitOnError": true
      }
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Strict Mode
Chapter 15 · Page 107
Intermediate

TypeScript Strict Mode

(continued)

Implicit Any Errors

  • Require types for variables without explicit type annotations.
    // With noImplicitAny: error
    function add(a, b) {
      return a + b;
    }
    
    // Fixed: explicit types
    function add(a: number, b: number): number {
      return a + b;
    }
  • Enable noImplicitAny to catch typing mistakes early.
    // Error: parameter 'event' implicitly has an 'any' type
    document.addEventListener("click", (event) => { });
    
    // Fixed
    document.addEventListener("click", (event: MouseEvent) => { });
  • Annotate callback parameters that TypeScript cannot infer.
    // Error: 'item' implicitly has type 'any'
    const items = JSON.parse(data);
    items.forEach((item) => console.log(item));
    
    // Fixed: type the parsed value
    const items: string[] = JSON.parse(data);
    items.forEach((item: string) => console.log(item));
  • Use unknown instead of any when the type is genuinely unknown.
    function process(input: unknown) {
      if (typeof input === "string") {
        console.log(input.toUpperCase()); // Safe after narrowing
      }
    }
  • Reduces bugs from unintended type mismatches throughout the codebase.
    // Without noImplicitAny: silent runtime bug
    function double(n) { return n * 2; }
    double("3"); // "33" at runtime — no compile-time error
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Strict Mode
Chapter 15 · Page 108
Intermediate

TypeScript Strict Mode

(continued)

Null Check Errors

  • Treat null and undefined as distinct types.
    // With strictNullChecks: error
    const name: string = null;
    
    // Fixed: include null in type
    const name: string | null = null;
  • Require null checks before accessing properties.
    function getName(user: User | null) {
      if (!user) return "Guest";
      return user.name; // Safe: user is not null here
    }
  • Prevents many common runtime errors from unchecked nulls.
    // Error: Object is possibly undefined
    const first = items[0].name;
    
    // Fixed
    const first = items[0]?.name ?? "Unknown";
  • Enable strictNullChecks independently before enabling full strict mode.
    {
      "compilerOptions": {
        "strictNullChecks": true
      }
    }
  • Most impactful strict flag — catching nullable bugs early saves debugging time.
    // Without strictNullChecks: no error, crashes at runtime
    const user = getUser(); // may return null
    console.log(user.name); // TypeError: Cannot read properties of null
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Strict Mode
Chapter 15 · Page 109
Intermediate

TypeScript Strict Mode

(continued)

Property Init Errors

  • Require class properties to be initialized in the constructor.
    // With strictPropertyInitialization: error
    class User {
      name: string;
    }
    
    // Fixed: initialize in constructor
    class User {
      name: string;
      constructor(name: string) {
        this.name = name;
      }
    }
  • Use definite assignment assertion (!) if initialization is deferred.
    class User {
      name!: string; // tells TypeScript: trust me, this will be set
      
      async init() {
        this.name = await fetchName();
      }
    }
  • Initialize properties with a default value to satisfy the compiler.
    class Config {
      retries: number = 3;
      timeout: number = 5000;
      debug: boolean = false;
    }
  • Use readonly with constructor initialization for immutable properties.
    class Product {
      readonly id: string;
      readonly name: string;
      
      constructor(id: string, name: string) {
        this.id = id;
        this.name = name;
      }
    }
  • Mark properties that are set outside the constructor with declare if needed.
    class Widget {
      declare element: HTMLElement; // set by framework before use
      
      render() {
        this.element.innerHTML = "Hello";
      }
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Strict Mode
Chapter 15 · Page 110
Intermediate

TypeScript Strict Mode

(continued)

Progressive Migration

  • Disable strict mode initially for large migrations.
    {
      "compilerOptions": {
        "strict": false
      }
    }
  • Enable individual flags gradually as code is typed.
    {
      "compilerOptions": {
        "strict": false,
        "noImplicitAny": true
      }
    }
  • Use stricter settings for new code while migrating old code.
    {
      "compilerOptions": {
        "strict": false
      },
      "ts-node": {
        "compilerOptions": {
          "strict": true
        }
      }
    }
  • Gradually increase strictness until full strict mode is enabled.
    {
      "compilerOptions": {
        "noImplicitAny": true,
        "strictNullChecks": true,
        "strictPropertyInitialization": true,
        "strict": true
      }
    }
  • Use @ts-strict-ignore comments to suppress errors in files not yet migrated.
    // @ts-strict-ignore
    // This file is pending strict migration — tracked in issue #42
    export function legacyHelper(data) {
      return data.value;
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Strict Mode
Chapter 15 · Page 111
Intermediate

TypeScript Strict Mode

(FAQ)

FAQ

It enables a bundle of flags including strictNullChecks, noImplicitAny, strictFunctionTypes, strictPropertyInitialization, and others. You can enable each flag individually if you want finer control during migration.

Use optional chaining (obj?.prop), nullish coalescing (val ?? default), or explicit null checks (if (val !== null)) before accessing the value. Type narrowing with typeof or instanceof also works for more complex cases.

Not directly per-file, but you can use multiple tsconfig.json files — a strict one for new code and a looser one for legacy code — by splitting source directories and using project references.

noImplicitAny is one of several flags that strict mode bundles together; enabling strict: true is equivalent to enabling noImplicitAny plus strictNullChecks, strictFunctionTypes, strictBindCallApply, and strictPropertyInitialization all at once.

strictPropertyInitialization requires that class properties are either initialized in the declaration or definitely assigned in the constructor. Use a definite assignment assertion (prop!: Type) if you know it will be set externally, or initialize it to a default value.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Type Narrowing
Chapter 16 · Page 112
Intermediate

TypeScript Type Narrowing

Master typeof, instanceof, in, and discriminated unions to safely work with union types.

TL;DR

  1. 01Narrow union types using typeof, instanceof, and in checks.
  2. 02Use discriminated unions for safer object type narrowing.
  3. 03Build custom type guards with the is keyword.

Tips

  1. 01Reach for discriminated unions when modeling state, since they pair perfectly with switch statements and exhaustive narrowing.

Warnings

  1. 01A failing assertion function throws at runtime, so always wrap calls in try/catch when the input is untrusted.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Type Narrowing
Chapter 16 · Page 113
Intermediate

TypeScript Type Narrowing

(continued)

Typeof Checks

  • Use typeof to narrow primitive types like string, number, and boolean.
    function format(value: string | number) {
      if (typeof value === "string") {
        return value.trim();
      }
      return value.toFixed(2);
    }
  • TypeScript automatically narrows the type inside each branch.
  • Works for string, number, boolean, bigint, symbol, undefined, and function.
  • This is the simplest form of narrowing and works with most union types.
  • Use it whenever a union mixes primitive types together.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Type Narrowing
Chapter 16 · Page 114
Intermediate

TypeScript Type Narrowing

(continued)

Instanceof and Truthiness

  • Use instanceof to narrow object types created from classes.
    if (error instanceof Error) {
      console.log(error.message);
    }
  • Check for null or undefined to narrow nullable union types.
    function greet(name: string | null) {
      if (name) return `Hi ${name}`;
      return "Hi friend";
    }
  • Truthiness checks narrow out falsy values like null, undefined, and empty string.
  • Use optional chaining ?. alongside narrowing for safer property access.
  • Combine with nullish coalescing ?? to provide default values.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Type Narrowing
Chapter 16 · Page 115
Intermediate

TypeScript Type Narrowing

(continued)

The In Operator

  • Use in to check if a property exists on an object.
    type Dog = { bark(): void };
    type Cat = { meow(): void };
    function speak(pet: Dog | Cat) {
      if ("bark" in pet) pet.bark();
      else pet.meow();
    }
  • TypeScript narrows the type based on which property is present.
  • Useful when union members have different shapes without a common tag.
  • Works for both own properties and inherited ones on the object.
  • Pair with discriminated unions for even cleaner narrowing.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Type Narrowing
Chapter 16 · Page 116
Intermediate

TypeScript Type Narrowing

(continued)

Discriminated Unions

  • Add a shared literal property to union members for safe narrowing.
    type Shape =
      | { kind: "circle"; radius: number }
      | { kind: "square"; side: number };
    
    function area(shape: Shape) {
      if (shape.kind === "circle") {
        return Math.PI * shape.radius ** 2;
      }
      return shape.side ** 2;
    }
  • The shared kind property acts as a discriminator for narrowing.
  • TypeScript automatically narrows to the matching member in each branch.
  • Pair with a switch statement for clean handling of every case.
  • This is the most reliable pattern for narrowing complex unions.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Type Narrowing
Chapter 16 · Page 117
Intermediate

TypeScript Type Narrowing

(continued)

Custom Type Guards

  • Write a function that returns a type predicate using the is keyword.
    function isString(value: unknown): value is string {
      return typeof value === "string";
    }
  • The compiler uses the predicate to narrow types in calling code.
    if (isString(value)) {
      console.log(value.toUpperCase());
    }
  • Use type guards for complex checks that built-in operators cannot express.
  • Use asserts value is Type for guards that throw on failure.
    function assertString(v: unknown): asserts v is string {
      if (typeof v !== "string") throw new Error("Not a string");
    }
  • Custom guards keep narrowing logic reusable across your codebase.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Type Narrowing
Chapter 16 · Page 118
Intermediate

TypeScript Type Narrowing

(FAQ)

FAQ

Use a typeof check: if (typeof value === 'string') { ... }. Inside that block TypeScript automatically narrows value to string, giving you full type-safe access to string methods.

Use discriminated unions whenever a variable can represent multiple distinct states—for example, a result that is either { status: 'ok', data: T } or { status: 'error', message: string }. The shared literal field lets TypeScript narrow exhaustively in a switch statement, catching unhandled cases at compile time.

instanceof checks the prototype chain and works for class instances (e.g., if (err instanceof Error)). The in operator checks for property existence and is the right choice for plain objects and interfaces, since interfaces have no runtime constructor to test against.

Define a function with a return type of the form value is SomeType: function isCat(animal: Animal): animal is Cat { return (animal as Cat).purr !== undefined; }. When this function returns true, TypeScript narrows the argument to Cat in the calling scope.

Assertion functions (those that assert value is T) throw an error rather than returning false, so unhandled exceptions will crash your program. Guard any assertion call on untrusted data—like API responses—with a try/catch block, or use a boolean type guard instead when the input may legitimately not match.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript With React
Chapter 17 · Page 119
Intermediate

TypeScript With React

Type React components, props, and hooks safely with TypeScript.

TL;DR

  1. 01Define prop interfaces or types for component APIs.
  2. 02Use React.FC<Props> or function return types for components.
  3. 03Extend built-in React types for events and refs.

Tips

  1. 01Export prop interfaces so consumers can extend or override them when needed for customization.

Warnings

  1. 01Avoid using the any type — specific types catch bugs at compile time and make refactoring safer.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript With React
Chapter 17 · Page 120
Intermediate

TypeScript With React

(continued)

Typing Component Props

  • Define props with an interface — use ? for optional props with defaults.

    interface ButtonProps {
      label: string;
      onClick: () => void;
      disabled?: boolean;
    }
    
    function Button({ label, onClick, disabled = false }: ButtonProps) {
      return <button onClick={onClick} disabled={disabled}>{label}</button>;
    }
    
  • Use type instead of interface when you need union or intersection props.

    type CardProps = {
      title: string;
      variant: "primary" | "secondary";
    };
    
    function Card({ title, variant }: CardProps) {
      return <div className={variant}><h2>{title}</h2></div>;
    }
    
  • Export prop interfaces so consumers can extend or build on them.

    export interface ButtonProps {
      label: string;
      onClick: () => void;
    }
    
    export function Button(props: ButtonProps) { /* ... */ }
    
  • Extend HTML element attributes to allow all native props on wrappers.

    interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
      label: string;
    }
    
    function LabeledInput({ label, ...rest }: InputProps) {
      return <label>{label}<input {...rest} /></label>;
    }
    
  • Use React.ComponentPropsWithoutRef to forward all props of a native tag.

    interface ButtonProps extends React.ComponentPropsWithoutRef<"button"> {
      variant: "primary" | "ghost";
    }
    
    function Button({ variant, ...rest }: ButtonProps) {
      return <button className={variant} {...rest} />;
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript With React
Chapter 17 · Page 121
Intermediate

TypeScript With React

(continued)

Event Handlers

  • Type a change handler for text inputs using React.ChangeEvent.

    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
      setName(e.target.value); // e.target.value is string
    };
    
  • Type a form submit handler using React.FormEventHandler.

    function Form() {
      const handleSubmit: React.FormEventHandler<HTMLFormElement> = (e) => {
        e.preventDefault();
        // handle form data
      };
      return <form onSubmit={handleSubmit}></form>;
    }
    
  • Type a mouse click handler using React.MouseEventHandler.

    const handleClick: React.MouseEventHandler<HTMLButtonElement> = (e) => {
      console.log(e.button); // 0 = left, 1 = middle, 2 = right
    };
    
  • Type a keyboard handler to respond to specific key presses.

    function SearchBox() {
      const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
        if (e.key === "Enter") submitSearch();
      };
      return <input onKeyDown={handleKeyDown} />;
    }
    
  • Define typed handler props in an interface for reusable form components.

    interface TableRowProps {
      onRowClick: (id: number, e: React.MouseEvent<HTMLTableRowElement>) => void;
    }
    
    function TableRow({ onRowClick }: TableRowProps) {
      return <tr onClick={(e) => onRowClick(1, e)}></tr>;
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript With React
Chapter 17 · Page 122
Intermediate

TypeScript With React

(continued)

Typing Hooks

  • Pass a union type to useState when the initial value can be null.

    const [count, setCount] = useState<number>(0);
    const [user, setUser] = useState<User | null>(null);
    
  • Type useRef for DOM elements and access it with optional chaining.

    const inputRef = useRef<HTMLInputElement>(null);
    
    function focus() {
      inputRef.current?.focus(); // safe — current may be null
    }
    
  • Type createContext with a default and wrap it in a typed custom hook.

    const UserContext = React.createContext<User | undefined>(undefined);
    
    function useUser() {
      const context = useContext(UserContext);
      if (!context) throw new Error("useUser needs provider");
      return context;
    }
    
  • Type useReducer with a discriminated union to cover every action shape.

    type Action =
      | { type: "INCREMENT"; payload: number }
      | { type: "DECREMENT"; payload: number };
    
    const reducer = (state: number, action: Action) => {
      switch (action.type) {
        case "INCREMENT": return state + action.payload;
        case "DECREMENT": return state - action.payload;
      }
    };
    
  • Use as const on the return array to get a precise tuple type from a custom hook.

    function useToggle(initial: boolean) {
      const [value, setValue] = useState(initial);
      const toggle = () => setValue(v => !v);
      return [value, toggle] as const; // [boolean, () => void]
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript With React
Chapter 17 · Page 123
Intermediate

TypeScript With React

(continued)

Children and Refs

  • Use React.ReactNode for children props — it accepts JSX, strings, or null.

    interface WrapperProps {
      children: React.ReactNode;
    }
    
    function Wrapper({ children }: WrapperProps) {
      return <div className="wrapper">{children}</div>;
    }
    
  • Use React.PropsWithChildren to add children to any existing props type.

    interface CardProps { title: string; }
    
    function Card({ title, children }: React.PropsWithChildren<CardProps>) {
      return <div><h2>{title}</h2>{children}</div>;
    }
    
  • Use useRef with a DOM type and access .current safely with optional chaining.

    const videoRef = useRef<HTMLVideoElement>(null);
    
    function play() {
      videoRef.current?.play(); // optional chain handles null
    }
    
  • Use React.forwardRef with explicit generic types for ref-forwarding components.

    const TextInput = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
      (props, ref) => <input ref={ref} {...props} />
    );
    
  • Distinguish RefObject (read-only current) from MutableRefObject (writable current).

    // React.RefObject<T>: current is readonly — for DOM refs via useRef(null)
    const domRef: React.RefObject<HTMLDivElement> = useRef(null);
    
    // React.MutableRefObject<T>: current is writable — for storing values
    const timerRef: React.MutableRefObject<number> = useRef(0);
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript With React
Chapter 17 · Page 124
Intermediate

TypeScript With React

(continued)

TypeScript Config for React

  • Set jsx to react-jsx to avoid importing React in every file.

    {
      "compilerOptions": {
        "jsx": "react-jsx"
      }
    }
    
  • Enable strict mode to activate all strict type checks at once.

    {
      "compilerOptions": {
        "strict": true
      }
    }
    
  • Enable noUncheckedIndexedAccess to catch undefined array access at compile time.

    {
      "compilerOptions": {
        "noUncheckedIndexedAccess": true
      }
    }
    // items[0] is now string | undefined instead of string
    
  • Use paths to create tsconfig aliases for cleaner imports across large projects.

    {
      "compilerOptions": {
        "paths": {
          "@components/*": ["./src/components/*"],
          "@lib/*": ["./src/lib/*"]
        }
      }
    }
    
  • Set composite: true to enable project references in monorepo packages.

    {
      "compilerOptions": {
        "composite": true,
        "declaration": true,
        "declarationMap": true
      }
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript With React
Chapter 17 · Page 125
Intermediate

TypeScript With React

(FAQ)

FAQ

Use React.ChangeEvent as the parameter type: (e: React.ChangeEvent) => void. Swap HTMLInputElement for HTMLSelectElement or HTMLTextAreaElement depending on the element.

React.FC implicitly typed children in older React versions and can hide return type mismatches, so many teams prefer plain functions with an explicit return type of JSX.Element or React.ReactElement. Either approach works — pick one and stay consistent across the codebase.

Pass a union type as the generic: useState<User | null>(null). TypeScript will then enforce null checks before you access properties on the state value.

Add a type parameter to the function signature: function List({ items, renderItem }: { items: T[]; renderItem: (item: T) => React.ReactNode }). Callers get full type inference on the array items and the render callback without any extra annotations.

Extend the corresponding React HTML attribute type: interface ButtonProps extends React.ButtonHTMLAttributes { variant: 'primary' | 'secondary' }. This gives you all native attributes for free, so spreading props onto the underlying element works without losing type safety.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript With React Patterns
Chapter 18 · Page 126
Intermediate

TypeScript With React Patterns

Common TypeScript patterns for React components, hooks, and props.

TL;DR

  1. 01Type props with interfaces or types for component APIs.
  2. 02Use React.FC<Props> or function return types for components.
  3. 03Extend event types from React for type-safe handlers.

Tips

  1. 01Use discriminated unions for any component that accepts fundamentally different prop shapes — it removes the need for runtime checks.

Warnings

  1. 01Avoid over-typing or using any — specific types prevent bugs and make refactoring safer.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript With React Patterns
Chapter 18 · Page 127
Intermediate

TypeScript With React Patterns

(continued)

Discriminated Union Props

  • Define a multi-variant component using a type union with a shared discriminant.

    type AlertProps =
      | { variant: "success"; message: string }
      | { variant: "error"; message: string; code: number };
    
    function Alert(props: AlertProps) {
      return <div className={props.variant}>{props.message}</div>;
    }
    
  • Access variant-specific props only after narrowing on the discriminant field.

    function Alert(props: AlertProps) {
      if (props.variant === "error") {
        console.log(props.code); // only available in "error" branch
      }
      return <div>{props.message}</div>;
    }
    
  • Use an exhaustive switch with never to catch unhandled variants at compile time.

    function renderBadge(props: BadgeProps): React.ReactNode {
      switch (props.variant) {
        case "count": return <span>{props.count}</span>;
        case "dot":   return <span className="dot" />;
        default: {
          const _never: never = props; // compile error if a variant is missing
          return null;
        }
      }
    }
    
  • Model a button that accepts either a label or an icon, but not both.

    type ButtonProps =
      | { kind: "label"; label: string }
      | { kind: "icon"; icon: React.ReactNode; ariaLabel: string };
    
    function Button(props: ButtonProps) {
      return props.kind === "label"
        ? <button>{props.label}</button>
        : <button aria-label={props.ariaLabel}>{props.icon}</button>;
    }
    
  • Require a message for error alerts but not for success alerts.

    type ToastProps =
      | { type: "success"; title: string }
      | { type: "error"; title: string; detail: string };
    
    // Callers must provide detail when type is "error"
    const t: ToastProps = { type: "error", title: "Failed", detail: "404" };
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript With React Patterns
Chapter 18 · Page 128
Intermediate

TypeScript With React Patterns

(continued)

Polymorphic Components

  • Define helper types that extract the props for any HTML element or component.

    type AsProps<E extends React.ElementType> = {
      as?: E;
    } & React.ComponentPropsWithoutRef<E>;
    
  • Build a polymorphic Box component that types native props based on the as tag.

    function Box<E extends React.ElementType = "div">(
      { as, ...rest }: AsProps<E>
    ) {
      const Tag = as ?? "div";
      return <Tag {...rest} />;
    }
    
    // <Box as="a" href="/"> — href is required and typed
    // <Box as="button" onClick={fn}> — onClick is typed for button
    
  • Use React.ElementType as a prop type for flexible tag or component injection.

    interface HeadingProps {
      as?: React.ElementType; // accepts "h1", "h2", MyComponent, etc.
      children: React.ReactNode;
    }
    
    function Heading({ as: Tag = "h1", children }: HeadingProps) {
      return <Tag>{children}</Tag>;
    }
    
  • Avoid unsafe casting with as unknown — let generics flow through instead.

    // Bad: loses type safety
    function Box({ as: Tag = "div", ...rest }: any) {
      return <Tag {...(rest as any)} />;
    }
    
    // Good: generics preserve prop types
    function Box<E extends React.ElementType = "div">({ as, ...rest }: AsProps<E>) {
      const Tag = as ?? "div";
      return <Tag {...rest} />;
    }
    
  • Pass a custom component as the as prop to reuse any component's interface.

    function Link({ href, children }: { href: string; children: React.ReactNode }) {
      return <a href={href}>{children}</a>;
    }
    
    // Box as Link — Box now accepts href because Link does
    <Box as={Link} href="/home">Home</Box>
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript With React Patterns
Chapter 18 · Page 129
Intermediate

TypeScript With React Patterns

(continued)

Forward Ref Patterns

  • Use React.forwardRef with explicit generics to type the ref and props correctly.

    const TextInput = React.forwardRef<
      HTMLInputElement,
      React.InputHTMLAttributes<HTMLInputElement>
    >((props, ref) => <input ref={ref} {...props} />);
    
  • Expose a custom handle interface using useImperativeHandle instead of the DOM ref.

    interface DialogHandle { open: () => void; close: () => void; }
    
    const Dialog = React.forwardRef<DialogHandle, { children: React.ReactNode }>(
      ({ children }, ref) => {
        const [open, setOpen] = useState(false);
        useImperativeHandle(ref, () => ({
          open: () => setOpen(true),
          close: () => setOpen(false)
        }));
        return open ? <div>{children}</div> : null;
      }
    );
    
  • Use the forwarded ref at the call site with useRef typed to the handle.

    const dialogRef = useRef<DialogHandle>(null);
    
    function Page() {
      return (
        <>
          <button onClick={() => dialogRef.current?.open()}>Open</button>
          <Dialog ref={dialogRef}>Hello</Dialog>
        </>
      );
    }
    
  • Forward refs in component libraries to let consumers control focus.

    const Select = React.forwardRef<HTMLSelectElement, React.SelectHTMLAttributes<HTMLSelectElement>>(
      (props, ref) => <select ref={ref} {...props} />
    );
    
    // Consumers can now do: const ref = useRef<HTMLSelectElement>(null);
    
  • Assign displayName to forwardRef components for better DevTools labels.

    const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
      (props, ref) => <input ref={ref} {...props} />
    );
    Input.displayName = "Input";
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript With React Patterns
Chapter 18 · Page 130
Intermediate

TypeScript With React Patterns

(continued)

Compound Components

  • Build a compound component by attaching child components as static properties.

    function Table({ children }: { children: React.ReactNode }) {
      return <table>{children}</table>;
    }
    
    Table.Row = function Row({ children }: { children: React.ReactNode }) {
      return <tr>{children}</tr>;
    };
    
    Table.Cell = function Cell({ children }: { children: React.ReactNode }) {
      return <td>{children}</td>;
    };
    
  • Use dot-notation at the call site to keep related components grouped.

    <Table>
      <Table.Row>
        <Table.Cell>Name</Table.Cell>
        <Table.Cell>Age</Table.Cell>
      </Table.Row>
    </Table>
    
  • Share state between compound parts using context.

    const AccordionCtx = React.createContext<{ open: string; setOpen: (id: string) => void } | null>(null);
    
    function Accordion({ children }: { children: React.ReactNode }) {
      const [open, setOpen] = useState("");
      return <AccordionCtx.Provider value={{ open, setOpen }}>{children}</AccordionCtx.Provider>;
    }
    
  • Type the static child properties explicitly on the parent component's type.

    interface TableComponent {
      (props: { children: React.ReactNode }): JSX.Element;
      Row: (props: { children: React.ReactNode }) => JSX.Element;
      Cell: (props: { children: React.ReactNode }) => JSX.Element;
    }
    
    const Table: TableComponent = ({ children }) => <table>{children}</table>;
    
  • Access shared context inside a child component to read parent state.

    Accordion.Item = function Item({ id, title, children }: { id: string; title: string; children: React.ReactNode }) {
      const ctx = useContext(AccordionCtx)!;
      return (
        <div>
          <button onClick={() => ctx.setOpen(id)}>{title}</button>
          {ctx.open === id && children}
        </div>
      );
    };
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript With React Patterns
Chapter 18 · Page 131
Intermediate

TypeScript With React Patterns

(continued)

Type Guards in Components

  • Write a type guard function to narrow a union type inside a component.

    interface User { kind: "user"; name: string; }
    interface Admin { kind: "admin"; name: string; permissions: string[]; }
    
    function isAdmin(person: User | Admin): person is Admin {
      return person.kind === "admin";
    }
    
  • Use the in operator as an inline type guard to check for a key's presence.

    function ProfileCard({ person }: { person: User | Admin }) {
      return (
        <div>
          <p>{person.name}</p>
          {"permissions" in person && <p>Permissions: {person.permissions.join(", ")}</p>}
        </div>
      );
    }
    
  • Narrow a discriminated union inside render using the kind field.

    function Notification(props: { type: "info"; text: string } | { type: "alert"; text: string; level: number }) {
      if (props.type === "alert") {
        return <div className={`alert-${props.level}`}>{props.text}</div>;
      }
      return <div>{props.text}</div>;
    }
    
  • Assert a type inside an event handler when you know the target element.

    function Form() {
      const handleChange = (e: React.ChangeEvent<HTMLElement>) => {
        if (e.target instanceof HTMLInputElement) {
          console.log(e.target.value); // now typed as string
        }
      };
      return <input onChange={handleChange} />;
    }
    
  • Use a type guard to safely render only the correct variant of a union prop.

    type Content = { kind: "text"; body: string } | { kind: "image"; src: string; alt: string };
    
    function isImageContent(c: Content): c is Extract<Content, { kind: "image" }> {
      return c.kind === "image";
    }
    
    function Render({ content }: { content: Content }) {
      return isImageContent(content)
        ? <img src={content.src} alt={content.alt} />
        : <p>{content.body}</p>;
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript With React Patterns
Chapter 18 · Page 132
Intermediate

TypeScript With React Patterns

(FAQ)

FAQ

Prefer explicit return types like : JSX.Element or : React.ReactElement over React.FC<Props>React.FC implicitly includes children in older versions and adds unnecessary complexity. Typing the function directly gives you cleaner inference and more control.

Use React.ChangeEvent<HTMLInputElement> as the event type: onChange: (e: React.ChangeEvent<HTMLInputElement>) => void. React exports event types for all DOM elements, so swap HTMLInputElement for the appropriate element type like HTMLSelectElement or HTMLTextAreaElement.

Declare a type parameter on the function: function List<T>({ items, render }: { items: T[]; render: (item: T) => React.ReactNode }). This lets callers pass any array type while still getting full type safety on the items.

Both work equally well for component props, but interface supports declaration merging, which is useful when library consumers need to extend your prop types. Use type when you need union types or mapped types that interface can't express.

Add as const to the returned array: return [value, setValue] as const — without it, TypeScript widens the type to (string | Dispatch<...>)[] instead of the precise tuple type. Alternatively, explicitly annotate the return type as [string, Dispatch<SetStateAction<string>>].

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Advanced Types
Chapter 19 · Page 133
Advanced

TypeScript Advanced Types

Learn conditional types, mapped types, template literals, and advanced pattern matching.

TL;DR

  1. 01Use conditional types for type-level if-else logic.
  2. 02Use mapped types to transform keys and values in existing types.
  3. 03Use template literal types for string manipulation at the type level.

Tips

  1. 01Use conditional types with infer to extract and reuse parts of complex types in your type transformations.

Warnings

  1. 01Advanced types are powerful but can become hard to read — document complex type transformations and keep them focused on single concerns.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Advanced Types
Chapter 19 · Page 134
Advanced

TypeScript Advanced Types

(continued)

Conditional Types

  • Write type-level if-else logic using the T extends X ? Y : Z ternary syntax.

    type IsString<T> = T extends string ? true : false;
    
    type A = IsString<"hello">;  // true
    type B = IsString<number>;   // false
    
  • Use infer to capture and reuse a type nested inside a generic structure.

    type Flatten<T> = T extends Array<infer U> ? U : T;
    
    type Str = Flatten<string[]>;  // string
    type Num = Flatten<number>;    // number
    
  • Extract function return types without importing the function itself.

    type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
    
    type NumReturn = ReturnType<() => number>;  // number
    type StrReturn = ReturnType<() => string>;  // string
    
  • Distribute conditional types over union members automatically.

    type ToArray<T> = T extends any ? T[] : never;
    
    type Result = ToArray<string | number>;  // string[] | number[]
    
  • Prevent distribution by wrapping T in a tuple when you need one result.

    type IsUnion<T> = [T] extends [infer U] ? (U extends T ? false : true) : false;
    
    type A = IsUnion<string | number>;  // true
    type B = IsUnion<string>;           // false
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Advanced Types
Chapter 19 · Page 135
Advanced

TypeScript Advanced Types

(continued)

Mapped Types

  • Transform every key in an object type by iterating over keyof.

    type MyReadonly<T> = {
      readonly [K in keyof T]: T[K];
    };
    
    type User = { name: string; age: number };
    type ReadonlyUser = MyReadonly<User>;
    // { readonly name: string; readonly age: number }
    
  • Make all properties optional by adding ? in a mapped type.

    type Optional<T> = {
      [K in keyof T]?: T[K];
    };
    
    type PartialUser = Optional<User>; // { name?: string; age?: number }
    
  • Rename keys with the as clause using template literal types.

    type Getters<T> = {
      [K in keyof T as `get${Capitalize<K & string>}`]: () => T[K];
    };
    
    type UserGetters = Getters<User>;
    // { getName: () => string; getAge: () => number }
    
  • Filter keys by combining mapped types with conditional types.

    type StringKeys<T> = {
      [K in keyof T as T[K] extends string ? K : never]: T[K];
    };
    
    type User = { id: number; name: string; email: string };
    type StringOnly = StringKeys<User>; // { name: string; email: string }
    
  • Build a Setters utility by inverting Getters with the mapped as pattern.

    type Setters<T> = {
      [K in keyof T as `set${Capitalize<K & string>}`]: (value: T[K]) => void;
    };
    
    type UserSetters = Setters<User>;
    // { setName: (value: string) => void; setAge: (value: number) => void }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Advanced Types
Chapter 19 · Page 136
Advanced

TypeScript Advanced Types

(continued)

Template Literal Types

  • Build string types at the type level using backtick template syntax.

    type Event<T extends string> = `on${Capitalize<T>}`;
    
    type ClickEvent = Event<"click">;   // "onClick"
    type ChangeEvent = Event<"change">; // "onChange"
    
  • Combine a union with a template to generate all string combinations.

    type Path = "user" | "post" | "comment";
    type GetRoute = `GET /${Path}`;
    // "GET /user" | "GET /post" | "GET /comment"
    
  • Extract the prefix of a string type with infer inside a template.

    type GetPrefix<T extends string> =
      T extends `${infer Prefix}_${string}` ? Prefix : never;
    
    type P = GetPrefix<"user_id">;    // "user"
    type Q = GetPrefix<"post_slug">;  // "post"
    
  • Split a string type into a tuple using recursive conditional types.

    type Split<T extends string, D extends string> =
      T extends `${infer F}${D}${infer R}` ? [F, ...Split<R, D>] : [T];
    
    type Parts = Split<"a-b-c", "-">;  // ["a", "b", "c"]
    
  • Build CSS-property-like types from a set of base names.

    type Side = "top" | "right" | "bottom" | "left";
    type Margin = `margin-${Side}`;
    // "margin-top" | "margin-right" | "margin-bottom" | "margin-left"
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Advanced Types
Chapter 19 · Page 137
Advanced

TypeScript Advanced Types

(continued)

Advanced Utility Patterns

  • Build DeepPartial to make every nested property optional recursively.

    type DeepPartial<T> = T extends object
      ? { [K in keyof T]?: DeepPartial<T[K]> }
      : T;
    
  • Build DeepReadonly to freeze every level of a nested object type.

    type DeepReadonly<T> = T extends object
      ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
      : T;
    
  • Check type assignability at the type level with a boolean predicate.

    type IsAssignableTo<T, U> = T extends U ? true : false;
    
    type A = IsAssignableTo<"hello", string>; // true
    type B = IsAssignableTo<number, string>;  // false
    
  • Build RequireKeys to make selected optional fields required.

    type RequireKeys<T, K extends keyof T> =
      Omit<T, K> & Required<Pick<T, K>>;
    
    type User = { id?: number; name?: string; email?: string };
    type UserWithId = RequireKeys<User, "id">;
    // { id: number; name?: string; email?: string }
    
  • Build a Validator type that maps each key to a validation function.

    type Validator<T> = {
      [K in keyof T]: (value: T[K]) => boolean;
    };
    
    // Usage: const userValidator: Validator<User> = { name: (v) => v.length > 0 };
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Advanced Types
Chapter 19 · Page 138
Advanced

TypeScript Advanced Types

(continued)

Practical Applications

  • Build type-safe API response types using conditional types on status.

    type ApiResponse<T extends "success" | "error"> =
      T extends "success"
        ? { status: 200; data: unknown }
        : { status: 400; error: string };
    
    type Ok = ApiResponse<"success">; // { status: 200; data: unknown }
    
  • Create a database query builder type using mapped types with as.

    type SelectBuilder<T> = {
      [K in keyof T as `select${Capitalize<K & string>}`]: () => T[K];
    };
    
    // SelectBuilder<User> gives: { selectName: () => string; selectId: () => number }
    
  • Extract promise resolution types for async function return values.

    type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
    
    type Data = Awaited<Promise<Promise<string>>>; // string
    
  • Build a type-safe event map using template literals and mapped types.

    type Events = { click: MouseEvent; keydown: KeyboardEvent };
    type OnEvents = {
      [K in keyof Events as `on${Capitalize<K & string>}`]: (e: Events[K]) => void;
    };
    // { onClick: (e: MouseEvent) => void; onKeydown: (e: KeyboardEvent) => void }
    
  • Use conditional types to unwrap nested result types safely.

    type UnwrapResult<T> = T extends { ok: true; value: infer V }
      ? V
      : T extends { ok: false; error: infer E }
      ? never
      : never;
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Advanced Types
Chapter 19 · Page 139
Advanced

TypeScript Advanced Types

(FAQ)

FAQ

Use infer within an extends clause to capture a type variable: type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never. This lets you pull out nested types like promise resolutions or function parameters without manually specifying them.

Mapped types iterate over keys to transform an existing type's structure (e.g., making all properties optional), while conditional types act like type-level ternaries to choose between types based on a condition. You often combine them — a mapped type whose values use a conditional type — for powerful transformations.

Yes — define a type like type EventName = on${Capitalize}`` to only accept strings matching that pattern. This catches misnamed event handlers or API keys at compile time rather than at runtime.

TypeScript limits recursive type instantiation depth to prevent infinite loops; this typically happens with unbounded recursive conditional types. Break the recursion with a depth counter tuple or split the type into smaller, composable helper types to stay within the limit.

Combine mapped types with conditional types: type RequireKeys<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>. This preserves optional fields outside K while enforcing presence for the keys you specify.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Decorators
Chapter 20 · Page 140
Advanced

TypeScript Decorators

Understand decorators, metadata, class transformations, and real-world patterns.

TL;DR

  1. 01Use decorators to annotate and modify classes and methods.
  2. 02TypeScript 5.0+ supports standard decorators natively — use experimentalDecorators only for legacy frameworks (NestJS/Angular with older setups)
  3. 03Apply metadata to decorate classes for frameworks and libraries.

Tips

  1. 01Use decorators sparingly and for cross-cutting concerns like logging, validation, or caching — avoid using them for simple business logic.

Warnings

  1. 01Standard decorators (TypeScript 5.0+, TC39 Stage 3) are stable. Only use experimentalDecorators: true if you're working with NestJS, Angular, or other frameworks that haven't yet migrated to the standard.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Decorators
Chapter 20 · Page 141
Advanced

TypeScript Decorators

(continued)

Class Decorators

  • Standard decorators work in TypeScript 5.0+ without any tsconfig flag. Use experimentalDecorators only for legacy frameworks (NestJS, Angular with older setups).
    # TypeScript 5.0+: no flag needed for standard decorators
    # Legacy only (NestJS / Angular):
  • Enable legacy decorators in tsconfig.json when required by your framework.
    {
      "compilerOptions": {
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
      }
    }
  • Create a class decorator that receives the class constructor.
    function Sealed(constructor: Function) {
      Object.seal(constructor);
      Object.seal(constructor.prototype);
    }
    
    @Sealed
    class User {
      name = "Alice";
    }
  • Modify or wrap classes to add functionality.
    function Timestamped<T extends { new(...args: any[]): {} }>(
      constructor: T
    ) {
      return class extends constructor {
        createdAt = new Date();
      };
    }
  • Factory decorators can return new classes with enhanced features.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Decorators
Chapter 20 · Page 142
Advanced

TypeScript Decorators

(continued)

Method and Property Decorators

  • Create method decorators to intercept function calls.
    function LogCalls(
      target: any,
      propertyKey: string,
      descriptor: PropertyDescriptor
    ) {
      const original = descriptor.value;
      
      descriptor.value = function(...args: any[]) {
        console.log(`Calling ${propertyKey}`, args);
        return original.apply(this, args);
      };
    }
    
    class User {
      @LogCalls
      setName(name: string) {
        this.name = name;
      }
    }
  • Property decorators receive the target and property name.
    function Validate(target: any, propertyKey: string) {
      let value: any;
      
      Object.defineProperty(target, propertyKey, {
        get() { return value; },
        set(newVal: any) {
          if (newVal.length < 3) throw new Error("Too short");
          value = newVal;
        }
      });
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Decorators
Chapter 20 · Page 143
Advanced

TypeScript Decorators

(continued)

Metadata and Reflection

  • Store metadata on classes and methods using the reflect-metadata library.
    import "reflect-metadata";
    
    function Route(path: string) {
      return function(target: any, propertyKey: string) {
        Reflect.defineMetadata("route:path", path, target, propertyKey);
      };
    }
    
    class Controller {
      @Route("/users")
      getUsers() { }
    }
  • Retrieve metadata to build frameworks and libraries.
    const path = Reflect.getMetadata("route:path", Controller.prototype, "getUsers");
    console.log(path); // "/users"
  • Use emitDecoratorMetadata to capture parameter and return types.
    function ValidateTypes(target: any, propertyKey: string) {
      const types = Reflect.getMetadata("design:paramtypes", target, propertyKey);
      // Types are [String, Number, etc.]
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Decorators
Chapter 20 · Page 144
Advanced

TypeScript Decorators

(continued)

Decorator Composition

  • Stack multiple decorators on the same class or method.
    @Sealed
    @Timestamped
    class User {
      @LogCalls
      @Validate
      setName(name: string) {
        this.name = name;
      }
    }
  • Decorators execute from bottom to top on the target.
  • Compose decorators to build complex functionality.
    function Chain(...decorators: any[]) {
      return (target: any, propertyKey: string) => {
        decorators.forEach(d => d(target, propertyKey));
      };
    }
  • Use composition to keep decorators focused on single concerns.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Decorators
Chapter 20 · Page 145
Advanced

TypeScript Decorators

(continued)

Real-World Patterns

  • Build validation decorators for class properties.
    function MinLength(min: number) {
      return function(target: any, propertyKey: string) {
        Reflect.defineMetadata(`validate:${propertyKey}`, { min }, target);
      };
    }
    
    class User {
      @MinLength(3)
      name: string;
    }
  • Create caching decorators for expensive methods.
    function Memoize(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
      const original = descriptor.value;
      const cache = new Map();
      
      descriptor.value = function(...args: any[]) {
        const key = JSON.stringify(args);
        if (cache.has(key)) return cache.get(key);
        
        const result = original.apply(this, args);
        cache.set(key, result);
        return result;
      };
    }
  • Use decorators in frameworks like NestJS for dependency injection.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Decorators
Chapter 20 · Page 146
Advanced

TypeScript Decorators

(FAQ)

FAQ

Add "experimentalDecorators": true to your tsconfig.json compilerOptions. If you need runtime metadata (e.g., for dependency injection), also enable "emitDecoratorMetadata": true and install the reflect-metadata package.

A class decorator receives the constructor function and can replace or modify the entire class, while a method decorator receives the target prototype, the method name, and its property descriptor — making it better suited for wrapping or intercepting individual method calls.

When composing decorators, they are applied bottom-up (innermost first) but evaluated top-down, so @A @B on a class means B's factory runs first but A's decorator function wraps the result last — the outermost decorator has the final say on the output.

Yes — combine a parameter decorator with a method decorator: the parameter decorator marks which params need validation using Reflect.metadata, and the method decorator wraps the function to check those marked params before execution runs.

No — TypeScript's legacy decorators (experimentalDecorators) follow an older spec and behave differently from the Stage 3 TC39 proposal. TypeScript 5.0+ supports the new standard decorators without the flag, but the APIs are incompatible, so migrating existing decorator code requires careful refactoring.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Function Overloading
Chapter 21 · Page 147
Advanced

TypeScript Function Overloading

A reference for declaring multiple type signatures on a single TypeScript function.

TL;DR

  1. 01Declare multiple overload signatures before the implementation function.
  2. 02Implement the function body using union types to handle all signatures.
  3. 03Callers see only the matching overload, not the implementation signature.

Tips

  1. 01Use overloading when a function behaves differently based on input types — it's better than union types for callers.

Warnings

  1. 01Overloads are compile-time only — the implementation still needs to handle all types at runtime.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Function Overloading
Chapter 21 · Page 148
Advanced

TypeScript Function Overloading

(continued)

Basic Function Overloading

  • Declare multiple overload signatures before the implementation.
    // Overload signatures
    function add(a: number, b: number): number;
    function add(a: string, b: string): string;
    
    // Implementation
    function add(a: any, b: any) {
      return a + b;
    }
    
    add(1, 2);     // OK: returns number
    add('a', 'b'); // OK: returns string
  • The implementation signature is not callable by consumers.
    function format(value: string): string;
    function format(value: number): string;
    
    function format(value: string | number): string {
      return String(value);
    }
    
    // format(true); // error — implementation signature is hidden
  • Use at least two overload signatures before the implementation.
    // One overload is not useful — use two or more
    function parse(input: string): number;
    function parse(input: number): string;
    
    function parse(input: string | number): string | number {
      return typeof input === "string" ? Number(input) : String(input);
    }
  • Overloads must be compatible with the implementation signature.
    function wrap(value: string): string[];
    function wrap(value: number): number[];
    
    function wrap(value: string | number): string[] | number[] {
      return [value as any];
    }
  • Use overloads to express arity differences explicitly.
    function createUser(name: string): User;
    function createUser(name: string, role: string): User;
    
    function createUser(name: string, role?: string): User {
      return { name, role: role ?? "user" };
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Function Overloading
Chapter 21 · Page 149
Advanced

TypeScript Function Overloading

(continued)

Optional Parameters

  • Use overloads to give different return types per argument count.
    function greet(name: string): string;
    function greet(name: string, age: number): string;
    
    function greet(name: string, age?: number) {
      if (age) {
        return `Hello, ${name}! You are ${age}.`;
      }
      return `Hello, ${name}!`;
    }
    
    greet('Alice');      // OK
    greet('Bob', 30);    // OK
  • Overloads prevent callers from passing invalid argument combinations.
    function connect(host: string): Connection;
    function connect(host: string, port: number): Connection;
    
    function connect(host: string, port?: number): Connection {
      return new Connection(host, port ?? 80);
    }
  • Optional overloads are clearer than one signature with many optional params.
    // Hard to reason about — too many optional flags
    // function render(a: string, b?: string, c?: string, d?: number): void
    
    // Better: separate overloads for each valid call pattern
    function render(template: string): void;
    function render(template: string, context: Record<string, string>): void;
    function render(template: string, context?: Record<string, string>): void {
      // implementation
    }
  • Name overload parameters consistently for readability.
    function fetch(url: string): Promise<Response>;
    function fetch(url: string, options: RequestInit): Promise<Response>;
    
    function fetch(url: string, options?: RequestInit): Promise<Response> {
      return window.fetch(url, options);
    }
  • Add a default-return overload last to handle the fallback case.
    function get(key: "count"): number;
    function get(key: "label"): string;
    function get(key: string): unknown;
    
    function get(key: string): unknown {
      return store[key];
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Function Overloading
Chapter 21 · Page 150
Advanced

TypeScript Function Overloading

(continued)

Different Return Types

  • Overloads let callers get the right return type per input type.
    function process(input: string): string;
    function process(input: number): number;
    
    function process(input: string | number): string | number {
      if (typeof input === 'string') {
        return input.toUpperCase();
      }
      return input * 2;
    }
    
    const str = process('hello'); // string
    const num = process(5);       // number
  • Without overloads, callers receive a union return type.
    // Without overloads — caller must narrow the return
    function convert(x: string | number): string | number {
      return typeof x === "string" ? x.toUpperCase() : x * 2;
    }
    
    const result = convert("hi"); // string | number — inconvenient
  • Map input literal types to different output types with overloads.
    function read(format: "json"): object;
    function read(format: "text"): string;
    function read(format: "buffer"): ArrayBuffer;
    
    function read(format: string): object | string | ArrayBuffer {
      // implementation chooses based on format
      return {};
    }
  • Use generics when the return type depends on the input type generically.
    // Overload approach
    function identity(x: string): string;
    function identity(x: number): number;
    function identity(x: any): any { return x; }
    
    // Generic approach — simpler for symmetric cases
    function identityG<T>(x: T): T { return x; }
  • Combine overloads with optional flags for conditional return shapes.
    function search(query: string, raw: true): string[];
    function search(query: string, raw?: false): SearchResult[];
    
    function search(query: string, raw?: boolean): string[] | SearchResult[] {
      const results = queryIndex(query);
      return raw ? results.map(r => r.raw) : results;
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Function Overloading
Chapter 21 · Page 151
Advanced

TypeScript Function Overloading

(continued)

Complex Overloads

  • Use overloads for functions that accept multiple input shapes.
    function merge(obj1: object, obj2: object): object;
    function merge(arr1: any[], arr2: any[]): any[];
    
    function merge(item1: any, item2: any) {
      if (Array.isArray(item1) && Array.isArray(item2)) {
        return [...item1, ...item2];
      }
      return { ...item1, ...item2 };
    }
  • Add overloads for callback vs non-callback variants of a function.
    function load(id: number): Promise<User>;
    function load(id: number, cb: (user: User) => void): void;
    
    function load(id: number, cb?: (user: User) => void): Promise<User> | void {
      const p = fetchUser(id);
      if (cb) { p.then(cb); } else { return p; }
    }
  • Overload class methods the same way as standalone functions.
    class Formatter {
      format(value: string): string;
      format(value: number): string;
      format(value: string | number): string {
        return String(value).trim();
      }
    }
  • Order overloads from most specific to least specific.
    function normalize(input: string[]): string[];
    function normalize(input: string): string;
    function normalize(input: string | string[]): string | string[] {
      return Array.isArray(input) ? input.map(s => s.trim()) : input.trim();
    }
  • Overload interface call signatures for typed function objects.
    interface Converter {
      (value: string): number;
      (value: number): string;
    }
    
    const convert: Converter = (value: any) =>
      typeof value === "string" ? Number(value) : String(value);
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Function Overloading
Chapter 21 · Page 152
Advanced

TypeScript Function Overloading

(continued)

Best Practices

  • Keep overloads focused so each signature is clearly distinct.
    // Good: overloads are distinct
    function getValue(key: string): string;
    function getValue(key: number): number;
    
    // Avoid: overlapping overloads confuse callers
    // function getValue(key: string | number): any { }
  • Avoid overloads when a generic achieves the same result more simply.
    // Overloads: verbose for symmetric relationships
    function echo(x: string): string;
    function echo(x: number): number;
    
    // Generic: cleaner for identity-like functions
    function echo<T>(x: T): T { return x; }
  • Document each overload signature with JSDoc for IDE tooltips.
    /** Converts input to uppercase */
    function transform(input: string): string;
    /** Doubles a numeric input */
    function transform(input: number): number;
    function transform(input: string | number): string | number {
      return typeof input === "string" ? input.toUpperCase() : input * 2;
    }
  • Test every declared overload in your test suite.
    // Verify each overload signature independently
    const a: string = transform("hello"); // must be string
    const b: number = transform(5);       // must be number
  • Don't create an overload if a union parameter already expresses the intent.
    // Use union when both inputs produce the same output type
    function printValue(value: string | number): void {
      console.log(String(value));
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Function Overloading
Chapter 21 · Page 153
Advanced

TypeScript Function Overloading

(FAQ)

FAQ

The overload signatures are what TypeScript checks against — if none of them match the argument types you're passing, the call fails at compile time even if your implementation would handle it. Add an overload signature that covers the case, or widen an existing one.

Use overloading when the return type changes based on input type, or when you want callers to get a precise return type without casting. Union parameters are simpler when the behavior and return type are the same regardless of input.

No — the implementation signature is invisible to callers. Only the overload signatures above it are part of the public API, so you must explicitly declare every callable variant before the implementation.

Declare multiple method signatures with the same name inside the class body, then write a single implementation that accepts union types. The syntax is identical to standalone function overloading — no 'function' keyword needed.

Not directly — the overload syntax only works with function declarations and class methods. For arrow functions, define an overloaded call signature on an interface or type alias and then assign a compatible function to a variable of that type.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Performance Tips
Chapter 22 · Page 154
Advanced

TypeScript Performance Tips

Profile code, optimize types, and identify performance bottlenecks in typed applications.

TL;DR

  1. 01Profile build time using tsc --diagnostics and --generateTrace flags.
  2. 02Avoid deeply nested generic types and large union types.
  3. 03Use import type to eliminate runtime overhead in compiled output.

Tips

  1. 01Profile your TypeScript compilation regularly to catch performance regressions early — small type optimizations add up.

Warnings

  1. 01Don't sacrifice type safety for build speed — focus on structural improvements, not disabling type checking.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Performance Tips
Chapter 22 · Page 155
Advanced

TypeScript Performance Tips

(continued)

Profiling Compilation

  • Run tsc with diagnostics to measure time spent in each compilation phase.

    tsc --diagnostics
    # Prints: Files, Lines, Nodes, Identifiers, Symbols, Types, Check time
    
  • Generate a trace file and open it in Chrome for deep performance analysis.

    tsc --generateTrace ./trace
    # Open chrome://tracing and load ./trace/trace.json
    
  • Enable incremental compilation to cache results and skip unchanged files.

    {
      "compilerOptions": {
        "incremental": true,
        "tsBuildInfoFile": "./build/.tsbuildinfo"
      }
    }
    
  • Measure total build time with the time command to establish a baseline.

    time tsc
    # real 0m4.321s — record this before making changes
    
  • Use extendedDiagnostics for a detailed per-phase breakdown of build time.

    tsc --extendedDiagnostics
    # Outputs: I/O Read, I/O Write, Parse, Bind, Check, Emit
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Performance Tips
Chapter 22 · Page 156
Advanced

TypeScript Performance Tips

(continued)

Optimizing Type Definitions

  • Avoid deeply nested generics — extract named intermediate types to reduce compiler work.

    // Bad: deeply nested forces repeated re-evaluation
    type Complex = Array<Record<string, Array<Promise<Result>>>>;
    
    // Good: named intermediate types are cached by the compiler
    type ResultPromise = Promise<Result>;
    type ResultMap = Record<string, ResultPromise[]>;
    type Complex = ResultMap[];
    
  • Keep union types small by grouping related members into subtypes.

    // Bad: large union is slow to distribute over
    type Status = "ok" | "done" | "error" | "failed"; // ... many more
    
    // Good: grouped subtypes are faster to check
    type SuccessStatus = "ok" | "done";
    type ErrorStatus = "error" | "failed";
    type Status = SuccessStatus | ErrorStatus;
    
  • Use const assertions to narrow literal types without adding runtime code.

    const roles = ["admin", "user", "guest"] as const;
    type Role = typeof roles[number]; // "admin" | "user" | "guest"
    
  • Prefer interface over type alias for object shapes — interfaces are cached by TypeScript.

    // interface: TypeScript caches the shape for faster re-use
    interface Config { host: string; port: number; }
    
    // type alias: re-evaluated each time it appears inline
    type Config = { host: string; port: number };
    
  • Extract shared subtypes to avoid repeating complex inline shapes in generics.

    // Better: shared subtype is computed and cached once
    type PageMeta = { total: number; page: number };
    type Response<T> = { data: T; meta: PageMeta };
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Performance Tips
Chapter 22 · Page 157
Advanced

TypeScript Performance Tips

(continued)

Type-Only Imports

  • Use import type to import types only — the compiler erases them at emit time.

    import type { User, Product } from "./types";
    
    const user: User = { id: 1, name: "Alice" };
    
  • Use inline type imports when mixing value and type imports from one module.

    import { readFile, type FileHandle } from "fs/promises";
    // readFile is a runtime value; FileHandle is erased at emit
    
  • Enable verbatimModuleSyntax in tsconfig to enforce type-only imports at compile time.

    {
      "compilerOptions": {
        "verbatimModuleSyntax": true
      }
    }
    
  • Use type-only re-exports to avoid pulling in runtime module side effects.

    // Safe: no runtime import of the whole module
    export type { User } from "./models/user";
    
    // Risky: may import the entire module at runtime
    export { User } from "./models/user";
    
  • Enforce no unused imports with ESLint to keep compiled output lean.

    {
      "rules": {
        "@typescript-eslint/no-unused-vars": "error"
      }
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Performance Tips
Chapter 22 · Page 158
Advanced

TypeScript Performance Tips

(continued)

Structural Sharing

  • Use declaration merging to extend an interface across multiple files cleanly.

    interface User { id: number; }
    
    interface User { name: string; }
    // User now has both id and name — no duplication needed
    
  • Extend base interfaces to share common fields across all entity types.

    interface BaseEntity { id: number; createdAt: Date; }
    
    interface User extends BaseEntity { name: string; }
    interface Post extends BaseEntity { title: string; }
    
  • Create a shared types file to avoid re-declaring the same shapes.

    // types/shared.ts
    export interface Pagination { page: number; size: number; total: number; }
    
    // Import once, use in multiple files
    import type { Pagination } from "../types/shared";
    
  • Compose types with intersection to combine existing shapes without duplication.

    type Audited = { createdBy: string; updatedBy: string };
    type Product = { id: number; name: string };
    type AuditedProduct = Product & Audited;
    
  • Use mapped types to derive Partial and Readonly variants from one source interface.

    interface User { id: number; name: string; email: string; }
    
    type UserPartial = Partial<User>;   // all fields optional
    type UserReadonly = Readonly<User>; // all fields readonly
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Performance Tips
Chapter 22 · Page 159
Advanced

TypeScript Performance Tips

(continued)

Build Tool Integration

  • Use esbuild for extremely fast bundling — it transpiles TypeScript without type checking.

    npm install -D esbuild
    esbuild src/index.ts --bundle --outfile=dist/index.js
    
  • Set skipLibCheck to skip type-checking of node_modules declaration files.

    {
      "compilerOptions": {
        "skipLibCheck": true
      }
    }
    
  • Use ts-loader with transpileOnly to skip type checking during webpack builds.

    {
      loader: 'ts-loader',
      options: {
        transpileOnly: true,       // skips type checking, much faster
        experimentalWatchApi: true
      }
    }
    
  • Use SWC to transpile fast, then run tsc separately for type checking only.

    # Transpile fast with SWC, check types in a separate step
    swc src -d dist
    tsc --noEmit  # type checking only, no JS output
    
  • Use project references to split monorepos into independently cached packages.

    {
      "references": [
        { "path": "./packages/core" },
        { "path": "./packages/ui" }
      ],
      "compilerOptions": { "composite": true }
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Performance Tips
Chapter 22 · Page 160
Advanced

TypeScript Performance Tips

(FAQ)

FAQ

Run tsc --diagnostics or tsc --extendedDiagnostics to get a breakdown of time spent on each compilation phase. For deeper analysis, use --generateTrace to produce a trace file you can load in Chrome DevTools.

Type-only imports (import type { Foo } from './foo') are erased entirely at emit time, so they never appear in compiled JavaScript. This reduces bundle size and prevents accidental runtime dependencies on modules that only contain types.

Deeply nested generics force the compiler to recursively instantiate and check complex type structures, which can exponentially increase type-checking time. Flatten or break them into intermediate named type aliases to give the compiler checkpoints and reduce redundant work.

Use project references (composite: true with tsc --build) so each package is compiled incrementally and results are cached. This avoids re-checking unchanged packages on every build and enables parallel compilation across projects.

skipLibCheck: true skips type-checking of .d.ts files in node_modules, which can meaningfully cut build time in large projects. It's a safe tradeoff in most cases since library types are typically pre-validated — just ensure your own source files still get full checking.

Useful Cheatsheetsusefulcheatsheets.com
TypeScript Utility Types
Chapter 23 · Page 161
Advanced

TypeScript Utility Types

A practical reference for TypeScript's built-in utility types including Partial, Pick, Omit, Record, ReturnType, and how to compose custom utilities.

TL;DR

  1. 01Utility types are generic helpers built into TypeScript that transform existing types into new ones without duplicating code.
  2. 02The most-used utilities — Partial, Required, Readonly, Pick, Omit, and Record — cover the vast majority of everyday type-transformation needs.
  3. 03You can compose multiple utility types together or build your own using mapped types and conditional types for any pattern the built-ins do not handle.

Tips

  1. 01Hover over any utility type in VS Code and TypeScript will expand the resolved type in the tooltip — a fast way to verify the transformation behaves exactly as you expect without running the compiler.
  2. 02Use <code>Omit</code> rather than <code>Pick</code> when extending a third-party interface — you avoid breakage when upstream adds new fields you would want to keep automatically without touching your type definition.
  3. 03<code>Awaited<T></code> (TypeScript 4.5+) recursively unwraps nested <code>Promise</code> wrappers. Combine it with <code>ReturnType</code> as <code>Awaited<ReturnType<typeof myFn>></code> to get the resolved value type of any async function in a single expression.

Warnings

  1. 01<code>Readonly<T></code> does not freeze the object at runtime — it only prevents compile-time reassignment. Use <code>Object.freeze()</code> for runtime immutability, or write a recursive <code>DeepReadonly</code> utility if nested object mutation must also be caught by the compiler.
  2. 02Deeply recursive utility types can slow TypeScript's language server noticeably on large union or object types. If autocomplete lags, add a depth-counter type parameter to short-circuit recursion after 5–10 levels.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Utility Types
Chapter 23 · Page 162
Advanced

TypeScript Utility Types

(continued)

What Utility Types Are

Utility types are generic type aliases shipped with TypeScript's standard library that accept one or more type arguments and return a transformed version of that type. They prevent you from hand-writing repetitive mapped types and keep your codebase consistent across teams.

All utility types are purely compile-time constructs — they emit zero JavaScript. They work by combining three lower-level features: mapped types ({ [K in keyof T]: ... }), conditional types (T extends U ? X : Y), and the infer keyword for extracting nested type information at the structural level.

Utility TypeInputWhat It ReturnsSince TS
Partial<T>Object typeAll properties made optional2.1
Required<T>Object typeAll optional props made required2.8
Readonly<T>Object typeAll properties read-only2.1
Pick<T, K>Object type + key unionSubset of T with only keys K2.1
Omit<T, K>Object type + key unionT minus keys K3.5
Record<K, V>Key union + value typeObject with keys K and values V2.1
ReturnType<T>Function typeFunction's return type2.8
Awaited<T>Promise or plain typeRecursively unwrapped value type4.5
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Utility Types
Chapter 23 · Page 163
Advanced

TypeScript Utility Types

(continued)

Partial, Required, and Readonly

Partial<T> makes every property in T optional by adding the ? modifier. It is most useful when writing update or PATCH functions where only a subset of fields may be present. It is shallow — nested object types are not recursively made optional.

Required<T> strips the ? modifier from every property, enforcing a fully-populated model. Use it after a factory function fills in defaults or after a validation step guarantees all fields are present.

Readonly<T> adds the readonly modifier to every property, causing the compiler to reject any mutations. Like the others, it is shallow by default.

UtilityModifier AppliedCommon Use CaseDeep?
Partial<T>Adds ? to all propsPATCH request body, form draft stateNo
Required<T>Removes ? from all propsPost-validation model, factory outputNo
Readonly<T>Adds readonly to all propsImmutable config objects, Redux stateNo
  • Use Partial<Pick<T, K>> to make only a specific subset of keys optional — a common pattern for form field groups.
  • The -? modifier in a custom mapped type removes optionality on a per-property basis, giving finer control than Required.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Utility Types
Chapter 23 · Page 164
Advanced

TypeScript Utility Types

(continued)

Pick, Omit, and Extract

Pick<T, K> constructs a new type containing only the keys listed in the union K. It is the right tool when a function or component needs an explicit subset of a larger model, making the dependency surface clear and narrow.

Omit<T, K> does the opposite — it removes listed keys and returns everything else. Prefer Omit when the excluded set is small but the kept set is large, especially when extending third-party interfaces whose future fields you want to keep automatically.

Extract<T, U> and its counterpart Exclude<T, U> operate on union types, not object types. Extract keeps union members assignable to U; Exclude removes them. NonNullable<T> is shorthand for Exclude<T, null | undefined>.

UtilityOperates OnExample InputResult
Pick<T, K>Object typePick<User, 'id'|'name'>{ id: number; name: string }
Omit<T, K>Object typeOmit<User, 'password'>User without the password field
Extract<T, U>Union typeExtract<'a'|'b'|'c', 'a'|'c'>'a' | 'c'
Exclude<T, U>Union typeExclude<'a'|'b'|'c', 'a'>'b' | 'c'
NonNullable<T>Union typeNonNullable<string|null>string
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Utility Types
Chapter 23 · Page 165
Advanced

TypeScript Utility Types

(continued)

Record, ReturnType, and Parameters

Record<K, V> builds an object type whose keys are members of the union K and whose values are all of type V. It is ideal for lookup tables, enum-keyed maps, and exhaustive switch alternatives where every key must be explicitly handled.

ReturnType<T> extracts the return type of a function type. This is invaluable when you depend on a function's output shape but cannot import its return type directly — for example, with third-party library factory functions that do not export their types.

Parameters<T> extracts parameter types as a tuple. ConstructorParameters<T> does the same for class constructors. InstanceType<T> extracts the class instance type from a constructor. Pair these with spread syntax to forward arguments type-safely through wrappers.

UtilityInput ExampleResolves To
Record<'USD'|'EUR', number>Key union + value type{ USD: number; EUR: number }
ReturnType<typeof fetch>Function typePromise<Response>
Awaited<ReturnType<typeof fetch>>Async function typeResponse
Parameters<typeof parseInt>Function type[string, (number | undefined)?]
InstanceType<typeof Date>Constructor typeDate
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Utility Types
Chapter 23 · Page 166
Advanced

TypeScript Utility Types

(continued)

Building Custom Utility Types

When the built-ins do not cover a pattern, build your own using mapped types, conditional types, and the infer keyword. Custom utilities follow the same generic signature as the built-ins and compose identically — they can be used as arguments to other utility types.

Common custom utilities include DeepPartial<T> (recursively optional), Mutable<T> (removes all readonly modifiers, useful in tests), and UnwrapPromise<T> (the pre-4.5 equivalent of Awaited). Keep these in a shared src/types/utils.ts file.

Custom UtilityDefinition PatternUse Case
DeepPartial<T>{ [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] }Deeply nested patch payloads
Mutable<T>{ -readonly [K in keyof T]: T[K] }Strip readonly in test setup
PickByValue<T, V>Mapped + conditional type filtering by value typeKeep only string-valued props
UnwrapPromise<T>T extends Promise<infer R> ? R : TPre-TS 4.5 Awaited equivalent
Nullable<T>{ [K in keyof T]: T[K] | null }Database row types that allow NULL
  • Use the -readonly and -? modifier prefixes in mapped types to remove existing modifiers rather than add them.
  • Add type-level unit tests using the tsd or expect-type package to catch regressions when TypeScript is upgraded.
Notes
Useful Cheatsheetsusefulcheatsheets.com
TypeScript Utility Types
Chapter 23 · Page 167
Advanced

TypeScript Utility Types

(FAQ)

FAQ

Pick creates a new type by selecting only the specified keys from a type, while Omit creates a new type by excluding the specified keys. Use Pick when you want a small subset of a large type, and Omit when you want most fields minus a few exceptions.

Yes, utility types are composable — for example, Partial<Pick<User, 'name' | 'email'>> creates a type with only those two fields made optional. You can chain as many transformations as needed to express complex type shapes concisely.

Use Record<K, V> when the set of keys is a known union type, such as Record<'admin' | 'user', Permission>, because it enforces that every key in the union is present. A plain index signature ({ [key: string]: V }) is more appropriate when keys are arbitrary and not enumerable at compile time.

ReturnType<T> extracts the return type of a function type, and Parameters<T> extracts its argument types as a tuple. Both are useful for deriving types from existing functions without duplicating type declarations, especially when working with third-party functions whose source types are not exported.

Custom utility types are written as generic types using mapped types (e.g., { [K in keyof T]: ... }) and conditional types (e.g., T extends U ? X : Y). For example, type Mutable<T> = { -readonly [K in keyof T]: T[K] } removes the readonly modifier from every property of T.

Preview: TypeScript Cheatsheets