Advanced

Master advanced TypeScript through 34 expert-level examples covering type-level programming, compiler internals, performance optimization, and framework integration patterns.

Example 52: Advanced Conditional Types with Distribution

Conditional types distribute over unions automatically. Understanding distribution unlocks powerful type transformations.

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph LR
    A["Type<br/>string | number"] --> B["Conditional Type<br/>T extends string"]
    B --> C["Distributes to:<br/>string | number"]
    C --> D["Results:<br/>true | false"]

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#029E73,stroke:#000,color:#fff
    style D fill:#CC78BC,stroke:#000,color:#fff

Code:

// DISTRIBUTIVE CONDITIONAL TYPE
type ToArray<T> = T extends any ? T[] : never; // => Distributes over union
// => Applied to each union member

type StringOrNumber = string | number;
type ArrayTypes = ToArray<StringOrNumber>; // => Type: string[] | number[]
// => NOT (string | number)[]

// NON-DISTRIBUTIVE CONDITIONAL TYPE
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never; // => Tuple wrapping prevents distribution

type ArrayTypesNonDist = ToArrayNonDist<StringOrNumber>; // => Type: (string | number)[]

// FILTERING UNION WITH DISTRIBUTION
type ExtractStrings<T> = T extends string ? T : never; // => Keep only strings

type Mixed = string | number | boolean;
type OnlyStrings = ExtractStrings<Mixed>; // => Type: string

// EXCLUDE TYPE (BUILT-IN)
type Exclude<T, U> = T extends U ? never : T; // => Remove U from T union

type WithoutNumber = Exclude<string | number | boolean, number>; // => Type: string | boolean

// EXTRACT TYPE (BUILT-IN)
type Extract<T, U> = T extends U ? T : never; // => Keep only T that extends U

type OnlyNumber = Extract<string | number | boolean, number>; // => Type: number

// CONDITIONAL TYPE WITH INFER
type UnwrapArray<T> = T extends (infer U)[] ? U : T; // => Extract array element type

type Numbers = UnwrapArray<number[]>; // => Type: number
type NotArray = UnwrapArray<string>; // => Type: string

// MULTIPLE INFER
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type Nested = Promise<Promise<string>>;
type First = UnwrapPromise<Nested>; // => Type: Promise<string> (one level)
type Second = UnwrapPromise<First>; // => Type: string (recursive application)

Key Takeaway: Conditional types distribute over unions by default. Wrap type in tuple [T] to prevent distribution. Use distribution for filtering and transforming union members.

Why It Matters: Distribution enables powerful union transformations. Extract and Exclude are built on distribution. Type-level filtering eliminates manual type definitions. Framework authors use distribution for generic type utilities. This pattern is fundamental to advanced TypeScript type programming.

Example 53: Recursive Conditional Types

Recursive types enable type-level iteration over complex structures. TypeScript 4.1+ supports recursive conditional types without depth limits.

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    A["Promise&lt;Promise&lt;string&gt;&gt;"] --> B["Unwrap Layer 1"]
    B --> C["Promise&lt;string&gt;"]
    C --> D["Unwrap Layer 2"]
    D --> E["string (base case)"]

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#029E73,stroke:#000,color:#fff
    style D fill:#CC78BC,stroke:#000,color:#fff
    style E fill:#CA9161,stroke:#000,color:#fff

Code:

// RECURSIVE PROMISE UNWRAPPING
type DeepUnwrapPromise<T> =
  T extends Promise<infer U> // => Check if T is Promise
    ? DeepUnwrapPromise<U> // => Recursive call with unwrapped type
    : T; // => Base case: return T

type Nested = Promise<Promise<Promise<string>>>;
type Unwrapped = DeepUnwrapPromise<Nested>; // => Type: string (all layers unwrapped)

// RECURSIVE ARRAY FLATTENING
type DeepFlatten<T> =
  T extends Array<infer U> // => Check if T is array
    ? DeepFlatten<U> // => Recursively flatten element type
    : T; // => Base case: return T

type NestedArray = number[][][];
type Flat = DeepFlatten<NestedArray>; // => Type: number (fully flattened)

// RECURSIVE OBJECT KEY PATHS
type DeepKeyPath<T> = T extends object // => Check if T is object
  ? {
      [K in keyof T]: K extends string // => Iterate over keys
        ? T[K] extends object // => Check if value is object
          ? `${K}` | `${K}.${DeepKeyPath<T[K]>}` // => Recursive path building
          : `${K}` // => Leaf key
        : never;
    }[keyof T]
  : never;

type User = {
  name: string;
  address: {
    street: string;
    city: string;
    coordinates: {
      lat: number;
      lng: number;
    };
  };
};

type UserPaths = DeepKeyPath<User>; // => Type: "name" | "address" | "address.street" | "address.city" | "address.coordinates" | "address.coordinates.lat" | "address.coordinates.lng"

// RECURSIVE JSON TYPE
type JSONValue =
  | string
  | number
  | boolean
  | null
  | JSONValue[] // => Recursive: array of JSONValue
  | { [key: string]: JSONValue }; // => Recursive: object with JSONValue values

const data: JSONValue = {
  name: "Alice",
  age: 30,
  tags: ["developer", "typescript"],
  nested: {
    deep: {
      value: 42,
    },
  },
}; // => Valid: matches recursive structure

Key Takeaway: Recursive conditional types enable deep type transformations. TypeScript 4.1+ removed recursion depth limits. Use base cases to prevent infinite recursion.

Why It Matters: Recursive types power deep object transformations in ORMs, schema generators, and type-safe form libraries. Form libraries can use recursive types for nested validation. ORMs can use recursion for deep relation types. This pattern is essential for framework-level type utilities.

Example 54: Template Literal Types - Advanced Patterns

Template literal types enable type-level string manipulation. Combine with mapped types and conditional types for powerful transformations.

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph LR
    A["'user'"] --> B["Capitalize"]
    B --> C["'User'"]
    C --> D["Add 'get'"]
    D --> E["'getUser'"]

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#029E73,stroke:#000,color:#fff
    style D fill:#CC78BC,stroke:#000,color:#fff
    style E fill:#CA9161,stroke:#000,color:#fff

Code:

// EVENT NAME GENERATION
type EventName<T extends string> = `on${Capitalize<T>}Change`; // => Template + built-in utility

type UserEvent = EventName<"user">; // => Type: "onUserChange"
type EmailEvent = EventName<"email">; // => Type: "onEmailChange"

// GETTER/SETTER GENERATION
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]; // => Mapped type with template literal key
};

type Setters<T> = {
  [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};

type Model = {
  name: string;
  age: number;
};

type ModelGetters = Getters<Model>; // => Type: { getName: () => string; getAge: () => number }
type ModelSetters = Setters<Model>; // => Type: { setName: (value: string) => void; setAge: (value: number) => void }

// CSS PROPERTY GENERATION
type CSSProperty<T extends string> = T | `${T}-top` | `${T}-right` | `${T}-bottom` | `${T}-left`; // => Union of variants

type Margin = CSSProperty<"margin">; // => Type: "margin" | "margin-top" | "margin-right" | "margin-bottom" | "margin-left"
type Padding = CSSProperty<"padding">; // => Type: "padding" | "padding-top" | "padding-right" | "padding-bottom" | "padding-left"

// HTTP METHOD + PATH TYPE
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint = `/api/${string}`;
type Request = `${HTTPMethod} ${Endpoint}`; // => Combines method and path

const validRequest: Request = "GET /api/users"; // => Valid
const validPost: Request = "POST /api/users/123"; // => Valid
// const invalid: Request = "INVALID /api/users"; // => Error: "INVALID" not in HTTPMethod

// EXTRACT ROUTE PARAMETERS
type ExtractParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` // => Match :param/rest
  ? Param | ExtractParams<`/${Rest}`> // => Extract param and recurse
  : T extends `${infer _Start}:${infer Param}` // => Match :param at end
    ? Param // => Extract final param
    : never; // => No params

type Route = "/users/:userId/posts/:postId";
type Params = ExtractParams<Route>; // => Type: "userId" | "postId"

Key Takeaway: Template literal types enable type-level string operations. Combine with mapped types for automatic API surface generation. Use recursion for parameter extraction.

Why It Matters: Template literals power type-safe routing, endpoint typing, and CSS-in-JS libraries. Styling libraries can use template literals for prop-based styling. Type generators can extract route parameters. This eliminates manual type definitions for string-based APIs.

Example 55: Variadic Tuple Types and Rest Elements

Variadic tuples enable type-safe operations on argument lists of unknown length. TypeScript 4.0+ supports multiple rest elements and inference.

// FUNCTION COMPOSITION WITH VARIADIC TUPLES
type Fn<Args extends any[], Return> = (...args: Args) => Return; // => Generic function type

type Compose<F extends Fn<any[], any>, G extends Fn<any[], any>> =
  F extends Fn<infer FArgs, infer FReturn> // => Infer F's signature
    ? G extends Fn<[FReturn], infer GReturn> // => G must accept F's return
      ? Fn<FArgs, GReturn> // => Composed function type
      : never
    : never;

const addOne = (x: number): number => x + 1; // => number → number
const toString = (x: number): string => x.toString(); // => number → string

type AddToString = Compose<typeof addOne, typeof toString>; // => Type: (x: number) => string

// TUPLE CONCATENATION
type Concat<T extends any[], U extends any[]> = [...T, ...U]; // => Spread both tuples

type Numbers = [1, 2, 3];
type Strings = ["a", "b"];
type Combined = Concat<Numbers, Strings>; // => Type: [1, 2, 3, "a", "b"]

// PARTIAL APPLICATION
type PartialApply<Fn extends (...args: any[]) => any, Applied extends any[]> = Fn extends (
  ...args: [...Applied, ...infer Rest]
) => infer Return // => Match applied + remaining
  ? (...args: Rest) => Return // => Return function with remaining params
  : never;

function greet(greeting: string, name: string, punctuation: string): string {
  return `${greeting} ${name}${punctuation}`; // => Combines all arguments
}

type GreetWithHello = PartialApply<typeof greet, [string]>; // => Type: (name: string, punctuation: string) => string
type GreetWithHelloAlice = PartialApply<typeof greet, [string, string]>; // => Type: (punctuation: string) => string

// CURRY TYPE
type Curry<Fn> = Fn extends (...args: infer Args) => infer Return // => Infer function signature
  ? Args extends [infer First, ...infer Rest] // => Split first argument
    ? (arg: First) => Curry<(...args: Rest) => Return> // => Recursive currying
    : Return // => Base case: no args left
  : never;

function add(a: number, b: number, c: number): number {
  return a + b + c; // => Sum three numbers
}

type CurriedAdd = Curry<typeof add>; // => Type: (arg: number) => (arg: number) => (arg: number) => number

// TUPLE TAIL
type Tail<T extends any[]> = T extends [any, ...infer Rest] ? Rest : []; // => Remove first element

type List = [1, 2, 3, 4];
type TailList = Tail<List>; // => Type: [2, 3, 4]

// TUPLE HEAD
type Head<T extends any[]> = T extends [infer First, ...any[]] ? First : never; // => Extract first element

type FirstElement = Head<List>; // => Type: 1

Key Takeaway: Variadic tuples enable type-safe function composition, partial application, and currying. Multiple rest elements allow complex tuple transformations.

Why It Matters: Variadic tuples power functional programming library typings. Action creator composition can use variadic tuples. Pipeline operators can rely on variadic tuple inference. Essential for type-safe higher-order functions.

Example 56: Type Inference with Infer Keyword Mastery

The infer keyword enables extracting types from generic positions. Master infer for advanced type-level programming.

// FUNCTION RETURN TYPE EXTRACTION
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; // => Built-in utility

function getUser() {
  return { name: "Alice", age: 30 }; // => Returns object
}

type UserType = ReturnType<typeof getUser>; // => Type: { name: string; age: number }

// FUNCTION PARAMETER EXTRACTION
type Parameters<T> = T extends (...args: infer P) => any ? P : never; // => Built-in utility

function createUser(name: string, age: number, active: boolean) {} // => Three parameters

type CreateUserParams = Parameters<typeof createUser>; // => Type: [string, number, boolean]

// FIRST PARAMETER EXTRACTION
type FirstParam<T> = T extends (first: infer F, ...args: any[]) => any ? F : never; // => Extract first param type

type FirstArg = FirstParam<typeof createUser>; // => Type: string

// CONSTRUCTOR PARAMETER EXTRACTION
type ConstructorParameters<T> = T extends new (...args: infer P) => any ? P : never; // => Built-in utility

class User {
  constructor(
    public name: string,
    public age: number,
  ) {} // => Constructor with two params
}

type UserConstructorParams = ConstructorParameters<typeof User>; // => Type: [string, number]

// INSTANCE TYPE EXTRACTION
type InstanceType<T> = T extends new (...args: any[]) => infer R ? R : never; // => Built-in utility

type UserInstance = InstanceType<typeof User>; // => Type: User

// NESTED INFER FOR ARRAY ELEMENT
type UnpackArray<T> = T extends (infer U)[] ? U : T; // => Extract array element

type NumberArray = number[];
type NumberElement = UnpackArray<NumberArray>; // => Type: number

// MULTIPLE INFER IN CONDITIONAL
type UnpackPromiseArray<T> = T extends Promise<infer U>[] // => Both Promise AND array
  ? U // => Extract Promise payload
  : never;

type PromiseNumbers = Promise<number>[];
type NumberFromPromise = UnpackPromiseArray<PromiseNumbers>; // => Type: number

// INFER IN OBJECT PROPERTY
type GetPropertyType<T, K extends keyof T> = T extends { [P in K]: infer V } // => Infer property type
  ? V
  : never;

type UserObj = { name: string; age: number };
type NameType = GetPropertyType<UserObj, "name">; // => Type: string

// CONTRAVARIANT INFER POSITIONS
type FunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never; // => Filter function properties
}[keyof T];

type Obj = {
  name: string;
  getName: () => string;
  setName: (name: string) => void;
};

type FuncKeys = FunctionPropertyNames<Obj>; // => Type: "getName" | "setName"

Key Takeaway: infer extracts types from generic positions in conditional types. Use multiple infer for complex extractions. Infer works in parameter, return, and property positions.

Why It Matters: Infer powers all TypeScript built-in utility types (ReturnType, Parameters, etc.). Type-safe dependency injection containers use infer for constructor parameter extraction. GraphQL code generators infer query result types. Essential for framework-level type manipulation.

Example 57: Branded Types for Runtime Safety

Branded types prevent mixing semantically different values of the same primitive type. Use phantom types for compile-time guarantees.

// BASIC BRANDED TYPE
type Brand<T, BrandName extends string> = T & { __brand: BrandName }; // => Intersection with phantom property

type UserId = Brand<number, "UserId">; // => Branded number
type ProductId = Brand<number, "ProductId">; // => Different brand

function getUserById(id: UserId): string {
  return `User ${id}`; // => Accepts only UserId
}

const userId = 123 as UserId; // => Brand casting
const productId = 456 as ProductId; // => Different brand

getUserById(userId); // => Valid
// getUserById(productId); // => Error: ProductId not assignable to UserId
// getUserById(123); // => Error: number not assignable to UserId

// BRANDED STRING TYPES
type Email = Brand<string, "Email">;
type Username = Brand<string, "Username">;

function sendEmail(to: Email, from: Email): void {
  console.log(`Sending email from ${from} to ${to}`); // => Type-safe email handling
}

const email = "alice@example.com" as Email; // => Branded string
const username = "alice" as Username; // => Different brand

sendEmail(email, email); // => Valid
// sendEmail(username, email); // => Error: Username not assignable to Email

// SMART CONSTRUCTOR PATTERN
function createEmail(raw: string): Email | null {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; // => Email validation regex
  return emailRegex.test(raw) ? (raw as Email) : null; // => Returns branded type or null
}

const validEmail = createEmail("alice@example.com"); // => Email | null
const invalidEmail = createEmail("not-an-email"); // => null

if (validEmail) {
  sendEmail(validEmail, validEmail); // => Type-safe after validation
}

// NUMERIC BRANDED TYPES WITH UNITS
type Kilometers = Brand<number, "Kilometers">;
type Miles = Brand<number, "Miles">;

function addKilometers(a: Kilometers, b: Kilometers): Kilometers {
  return (a + b) as Kilometers; // => Type-safe addition
}

function convertMilesToKilometers(miles: Miles): Kilometers {
  return (miles * 1.60934) as Kilometers; // => Explicit conversion
}

const km1 = 100 as Kilometers;
const km2 = 50 as Kilometers;
const miles = 10 as Miles;

addKilometers(km1, km2); // => Valid: same units
// addKilometers(km1, miles); // => Error: can't mix units
const converted = convertMilesToKilometers(miles); // => Explicit conversion required
addKilometers(km1, converted); // => Valid after conversion

// OPAQUE TYPE WITH CLASS
class EmailAddress {
  private __brand!: "Email"; // => Private phantom property
  private constructor(public value: string) {} // => Private constructor

  static create(raw: string): EmailAddress | null {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(raw) ? new EmailAddress(raw) : null; // => Factory method
  }
}

function sendTypedEmail(to: EmailAddress): void {
  console.log(`Sending to ${to.value}`); // => Access validated value
}

const typedEmail = EmailAddress.create("alice@example.com");
if (typedEmail) {
  sendTypedEmail(typedEmail); // => Type-safe with validation
}

Key Takeaway: Branded types add semantic meaning to primitives at compile-time. Use phantom properties or private fields for branding. Smart constructors enforce validation.

Why It Matters: Branded types prevent bugs from mixing user IDs and product IDs, or kilometers and miles. Financial systems use branded types for currencies. Type-safe routing uses branded URL types. No runtime cost—pure compile-time safety.

Example 58: Type-Level Programming - Arithmetic

Type-level arithmetic enables compile-time computation. Use tuple length manipulation for addition/subtraction.

// TYPE-LEVEL COUNTER WITH TUPLES
type Length<T extends any[]> = T["length"]; // => Extract tuple length

type Three = Length<[1, 2, 3]>; // => Type: 3
type Five = Length<[1, 1, 1, 1, 1]>; // => Type: 5

// BUILD TUPLE OF SPECIFIC LENGTH
type BuildTuple<L extends number, T extends any[] = []> = T["length"] extends L // => Check if reached target
  ? T // => Base case: return tuple
  : BuildTuple<L, [...T, any]>; // => Recursive: add element

type TupleOfFive = BuildTuple<5>; // => Type: [any, any, any, any, any]
type LengthOfTuple = Length<TupleOfFive>; // => Type: 5

// TYPE-LEVEL ADDITION
type Add<A extends number, B extends number> = Length<[...BuildTuple<A>, ...BuildTuple<B>]>; // => Concatenate tuples and get length

type TwoPlusThree = Add<2, 3>; // => Type: 5
type FourPlusSix = Add<4, 6>; // => Type: 10

// TYPE-LEVEL SUBTRACTION
type Subtract<A extends number, B extends number> =
  BuildTuple<A> extends [...BuildTuple<B>, ...infer Rest] // => Remove B elements from A
    ? Length<Rest> // => Return remaining length
    : never;

type FiveMinusTwo = Subtract<5, 2>; // => Type: 3
type TenMinusFour = Subtract<10, 4>; // => Type: 6

// TYPE-LEVEL COMPARISON
type GreaterThan<A extends number, B extends number> = Subtract<A, B> extends never ? false : true; // => If subtraction valid, A > B

type FiveGreaterThree = GreaterThan<5, 3>; // => Type: true
type TwoGreaterFive = GreaterThan<2, 5>; // => Type: false

// RANGE TYPE GENERATION
type Range<N extends number, Acc extends number[] = []> = Acc["length"] extends N // => Check if reached N
  ? Acc[number] // => Return union of array indices
  : Range<N, [...Acc, Acc["length"]]>; // => Recursive: add next number

type ZeroToFour = Range<5>; // => Type: 0 | 1 | 2 | 3 | 4
type ZeroToNine = Range<10>; // => Type: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

Key Takeaway: Use tuple length for type-level numbers. Tuple concatenation implements addition. Tuple pattern matching implements subtraction. Limited to small numbers due to recursion depth.

Why It Matters: Type-level arithmetic powers fixed-size array types, matrix dimensions in ML libraries, and compile-time validation. GraphQL fragment depth limits use type-level counting. Practical for compile-time bounds checking without runtime cost.

Example 59: Declaration Merging - Interface and Namespace

Declaration merging combines multiple declarations with the same name. TypeScript merges interfaces, namespaces, and module augmentation.

// INTERFACE MERGING (AUTOMATIC)
interface User {
  name: string; // => First declaration
}

interface User {
  age: number; // => Second declaration merges
}

const user: User = {
  name: "Alice", // => Combined interface
  age: 30, // => Requires both properties
};

// NAMESPACE AND FUNCTION MERGING
function createLogger(message: string) {
  console.log(message); // => Function behavior
}

namespace createLogger {
  export let version = "1.0.0"; // => Namespace property
  export function debug(message: string) {
    // => Namespace method
    console.log(`[DEBUG] ${message}`);
  }
}

createLogger("Info message"); // => Call as function
console.log(createLogger.version); // => Access namespace property: "1.0.0"
createLogger.debug("Debug message"); // => Call namespace method

// CLASS AND NAMESPACE MERGING
class Album {
  constructor(public title: string) {} // => Class constructor
}

namespace Album {
  export class Track {
    // => Nested class in namespace
    constructor(
      public name: string,
      public duration: number,
    ) {}
  }
  export function create(title: string): Album {
    // => Factory function
    return new Album(title);
  }
}

const album = new Album("Greatest Hits"); // => Class constructor
const track = new Album.Track("Song 1", 180); // => Nested class
const album2 = Album.create("Best Of"); // => Factory function

// ENUM AND NAMESPACE MERGING
enum Color {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE",
}

namespace Color {
  export function parse(value: string): Color | undefined {
    // => Parse string to enum
    return Object.values(Color).find((c) => c === value);
  }
  export function values(): Color[] {
    // => Get all enum values
    return Object.values(Color);
  }
}

const red = Color.Red; // => Enum member: "RED"
const parsed = Color.parse("GREEN"); // => Namespace function: Color.Green
const allColors = Color.values(); // => ["RED", "GREEN", "BLUE"]

// MODULE AUGMENTATION (EXTENDING THIRD-PARTY)
// Extend existing module
declare module "express" {
  interface Request {
    user?: { id: string; name: string }; // => Add user property
  }
}

// Now Request type includes user property globally

Key Takeaway: Interfaces merge automatically across declarations. Functions, classes, and enums can merge with namespaces. Module augmentation extends third-party types.

Why It Matters: Declaration merging powers middleware typing (extending Request/Response), component prop augmentation, and library plugin systems. Libraries can use namespace merging for plugins. Essential for extending external library types without forking.

Example 60: Global Augmentation for Built-in Types

Global augmentation extends built-in types and global namespaces. Use carefully to avoid polluting global scope.

// EXTENDING ARRAY PROTOTYPE TYPES
interface Array<T> {
  first(): T | undefined; // => Add first method
  last(): T | undefined; // => Add last method
}

// Implementation (would be in separate .js file)
Array.prototype.first = function () {
  return this[0]; // => Return first element
};

Array.prototype.last = function () {
  return this[this.length - 1]; // => Return last element
};

const numbers = [1, 2, 3, 4, 5];
const firstNum = numbers.first(); // => Type: number | undefined, Value: 1
const lastNum = numbers.last(); // => Type: number | undefined, Value: 5

// EXTENDING GLOBAL NAMESPACE
declare global {
  interface Window {
    // => Extend Window interface
    myApp: {
      version: string;
      apiUrl: string;
    };
  }

  var APP_VERSION: string; // => Global variable type

  namespace NodeJS {
    // => Extend NodeJS namespace
    interface ProcessEnv {
      DATABASE_URL: string; // => Type-safe env var
      API_KEY: string;
      NODE_ENV: "development" | "production" | "test";
    }
  }
}

// Usage (browser)
window.myApp = {
  // => Typed global object
  version: "1.0.0",
  apiUrl: "https://api.example.com",
};

// Usage (Node.js)
const dbUrl = process.env.DATABASE_URL; // => Type: string
const nodeEnv = process.env.NODE_ENV; // => Type: "development" | "production" | "test"

// EXTENDING STRING WITH CUSTOM METHODS
interface String {
  truncate(maxLength: number): string; // => Add truncate method
}

String.prototype.truncate = function (maxLength: number): string {
  return this.length > maxLength ? this.substring(0, maxLength) + "..." : this.toString(); // => Truncate with ellipsis
};

const longText = "This is a very long string";
const truncated = longText.truncate(10); // => "This is a ..."

// EXTENDING PROMISE WITH TIMEOUT
interface Promise<T> {
  timeout(ms: number): Promise<T>; // => Add timeout method
}

Promise.prototype.timeout = function <T>(this: Promise<T>, ms: number): Promise<T> {
  return Promise.race([
    this, // => Original promise
    new Promise<T>(
      (_, reject) => setTimeout(() => reject(new Error("Timeout")), ms), // => Timeout promise
    ),
  ]);
};

const fetchWithTimeout = fetch("https://api.example.com").timeout(5000); // => Fails if >5s

Key Takeaway: Global augmentation extends built-in types like Array, String, Window, and NodeJS namespaces. Use declare global for global scope extensions. Avoid excessive global pollution.

Why It Matters: Global augmentation enables type-safe environment variables (process.env), custom global objects (window extensions), and polyfill typing. Frameworks can use global augmentation for NodeJS.ProcessEnv. Essential for typing runtime-modified globals.

Example 61: Abstract Classes and Advanced Patterns

Abstract classes define contracts with partial implementation. Use for template method pattern and framework base classes.

// BASIC ABSTRACT CLASS
abstract class Animal {
  constructor(public name: string) {} // => Concrete property

  abstract makeSound(): string; // => Subclasses must implement

  move(distance: number): string {
    // => Concrete method
    return `${this.name} moved ${distance} meters`;
  }

  describe(): string {
    // => Template method using abstract
    return `${this.name} says ${this.makeSound()}`;
  }
}

class Dog extends Animal {
  makeSound(): string {
    // => Required implementation
    return "Woof!";
  }
}

const dog = new Dog("Rex");
console.log(dog.makeSound()); // => Output: "Woof!"
console.log(dog.move(10)); // => Output: "Rex moved 10 meters"
console.log(dog.describe()); // => Output: "Rex says Woof!"

// const animal = new Animal("Generic"); // => Error: Cannot instantiate abstract class

// ABSTRACT CLASS WITH PROTECTED MEMBERS
abstract class Repository<T> {
  protected abstract tableName: string; // => Subclasses define table

  protected abstract validate(item: T): boolean; // => Validation logic

  async save(item: T): Promise<void> {
    // => Template method
    if (!this.validate(item)) {
      // => Use abstract validation
      throw new Error("Validation failed");
    }
    console.log(`Saving to ${this.tableName}:`, item); // => Use abstract tableName
  }

  async findById(id: string): Promise<T | null> {
    // => Concrete shared logic
    console.log(`Finding in ${this.tableName} by id: ${id}`);
    return null; // => Placeholder
  }
}

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

class UserRepository extends Repository<User> {
  protected tableName = "users"; // => Concrete value

  protected validate(user: User): boolean {
    // => Concrete validation
    return user.email.includes("@"); // => Simple email check
  }

  async findByEmail(email: string): Promise<User | null> {
    // => Additional method
    console.log(`Finding user by email: ${email}`);
    return null;
  }
}

const userRepo = new UserRepository();
userRepo.save({ id: "1", name: "Alice", email: "alice@example.com" }); // => Valid
// userRepo.save({ id: "2", name: "Bob", email: "invalid" }); // => Throws: Validation failed

// ABSTRACT CLASS WITH FACTORY METHOD
abstract class PaymentProcessor {
  abstract createPayment(amount: number): Payment; // => Factory method

  process(amount: number): string {
    // => Template method
    const payment = this.createPayment(amount); // => Use factory
    return payment.execute(); // => Execute payment
  }
}

interface Payment {
  execute(): string;
}

class CreditCardPayment implements Payment {
  constructor(private amount: number) {}
  execute(): string {
    return `Processing credit card payment: $${this.amount}`;
  }
}

class PayPalPayment implements Payment {
  constructor(private amount: number) {}
  execute(): string {
    return `Processing PayPal payment: $${this.amount}`;
  }
}

class CreditCardProcessor extends PaymentProcessor {
  createPayment(amount: number): Payment {
    // => Factory implementation
    return new CreditCardPayment(amount);
  }
}

class PayPalProcessor extends PaymentProcessor {
  createPayment(amount: number): Payment {
    return new PayPalPayment(amount);
  }
}

const ccProcessor = new CreditCardProcessor();
console.log(ccProcessor.process(100)); // => "Processing credit card payment: $100"

const ppProcessor = new PayPalProcessor();
console.log(ppProcessor.process(50)); // => "Processing PayPal payment: $50"

Key Takeaway: Abstract classes provide partial implementation with enforced contracts. Use template method pattern for algorithm skeleton. Factory methods delegate object creation to subclasses.

Why It Matters: Abstract classes power ORM base classes, framework components, and plugin architectures. Class components can extend abstract bases. Essential for framework-level extensibility patterns.

Example 62: Mixin Pattern for Multiple Inheritance

Mixins enable composing behavior from multiple sources. TypeScript doesn’t support multiple inheritance but mixins provide similar functionality.

// BASIC MIXIN TYPE
type Constructor<T = {}> = new (...args: any[]) => T; // => Constructor type

// TIMESTAMPED MIXIN
function Timestamped<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    // => Return extended class
    createdAt = new Date(); // => Add timestamp property

    getAge(): number {
      // => Add age method
      return Date.now() - this.createdAt.getTime();
    }
  };
}

// ACTIVATABLE MIXIN
function Activatable<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    isActive = false; // => Add active state

    activate() {
      // => Add activate method
      this.isActive = true;
      console.log("Activated");
    }

    deactivate() {
      this.isActive = false;
      console.log("Deactivated");
    }
  };
}

// BASE CLASS
class User {
  constructor(public name: string) {}
}

// COMPOSE MIXINS
const TimestampedUser = Timestamped(User); // => Apply first mixin
const TimestampedActivatableUser = Activatable(TimestampedUser); // => Apply second mixin

const user = new TimestampedActivatableUser("Alice");
console.log(user.name); // => "Alice" (from base class)
console.log(user.createdAt); // => Date (from Timestamped)
console.log(user.isActive); // => false (from Activatable)
user.activate(); // => "Activated"
console.log(user.getAge()); // => Number (milliseconds since creation)

// TYPED MIXIN HELPER
function applyMixins(derivedCtor: any, constructors: any[]) {
  constructors.forEach((baseCtor) => {
    // => Iterate mixins
    Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
      // => Copy methods
      Object.defineProperty(
        derivedCtor.prototype,
        name,
        Object.getOwnPropertyDescriptor(baseCtor.prototype, name) || Object.create(null),
      );
    });
  });
}

// MIXIN WITH REQUIREMENTS
interface Named {
  name: string;
}

function Loggable<TBase extends Constructor<Named>>(Base: TBase) {
  // => Require 'name' property
  return class extends Base {
    log(message: string) {
      console.log(`[${this.name}] ${message}`); // => Use required property
    }
  };
}

class Product {
  constructor(
    public name: string,
    public price: number,
  ) {}
}

const LoggableProduct = Loggable(Product);
const product = new LoggableProduct("Widget", 29.99);
product.log("In stock"); // => "[Widget] In stock"

// CHAINABLE MIXINS
function Serializable<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    serialize(): string {
      // => Serialize to JSON
      return JSON.stringify(this);
    }

    static deserialize<T>(this: Constructor<T>, json: string): T {
      // => Deserialize from JSON
      const obj = JSON.parse(json);
      return new this(obj);
    }
  };
}

class Document {
  constructor(
    public title: string,
    public content: string,
  ) {}
}

const SerializableDocument = Serializable(Timestamped(Document)); // => Chain mixins
const doc = new SerializableDocument("Report", "Lorem ipsum");
const json = doc.serialize(); // => {"title":"Report","content":"Lorem ipsum","createdAt":"..."}

Key Takeaway: Mixins compose behavior without multiple inheritance. Use constructor types to extend base classes. Require interfaces for type-safe mixin constraints.

Why It Matters: Mixins power composition APIs, reusable hooks, and service composition. Utility libraries can use mixins for cross-cutting concerns (logging, serialization). Enables flexible behavior composition.

Example 63: Decorator Composition and Metadata

Decorators enable declarative metadata and behavior modification. Combine multiple decorators for powerful composition.

// ENABLE DECORATORS IN tsconfig.json:
// "experimentalDecorators": true
// "emitDecoratorMetadata": true

// CLASS DECORATOR
function Component(options: { selector: string }) {
  return function <T extends { new (...args: any[]): {} }>(constructor: T) {
    // => Decorator factory
    return class extends constructor {
      selector = options.selector; // => Add selector property
    };
  };
}

@Component({ selector: "app-user" })
class UserComponent {
  name = "User Component";
}

const component = new UserComponent();
console.log((component as any).selector); // => "app-user"

// METHOD DECORATOR
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value; // => Store original method

  descriptor.value = function (...args: any[]) {
    // => Replace method
    console.log(`Calling ${propertyKey} with`, args); // => Log before
    const result = originalMethod.apply(this, args); // => Call original
    console.log(`Result:`, result); // => Log after
    return result;
  };

  return descriptor;
}

class Calculator {
  @Log
  add(a: number, b: number): number {
    return a + b; // => Original logic
  }
}

const calc = new Calculator();
calc.add(5, 3); // => Logs: "Calling add with [5, 3]" then "Result: 8"

// PROPERTY DECORATOR
function ReadOnly(target: any, propertyKey: string) {
  Object.defineProperty(target, propertyKey, {
    writable: false, // => Make read-only
    configurable: false,
  });
}

class Config {
  @ReadOnly
  apiUrl = "https://api.example.com"; // => Immutable property
}

const config = new Config();
// config.apiUrl = "https://new-url.com"; // => Error: Cannot assign to read-only property

// PARAMETER DECORATOR
function Required(target: any, propertyKey: string, parameterIndex: number) {
  const existingRequiredParameters: number[] = Reflect.getOwnMetadata("required", target, propertyKey) || []; // => Get existing metadata
  existingRequiredParameters.push(parameterIndex); // => Add parameter index
  Reflect.defineMetadata("required", existingRequiredParameters, target, propertyKey); // => Store metadata
}

function Validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    const requiredParameters: number[] = Reflect.getOwnMetadata("required", target, propertyKey) || []; // => Get required params

    for (const index of requiredParameters) {
      if (args[index] === undefined || args[index] === null) {
        // => Check required params
        throw new Error(`Parameter at index ${index} is required`);
      }
    }

    return originalMethod.apply(this, args);
  };
}

class UserService {
  @Validate
  createUser(@Required name: string, @Required email: string, age?: number) {
    return { name, email, age }; // => Create user object
  }
}

const service = new UserService();
service.createUser("Alice", "alice@example.com"); // => Valid
// service.createUser("Bob", null as any); // => Throws: Parameter at index 1 is required

// DECORATOR COMPOSITION
function Retry(attempts: number) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = async function (...args: any[]) {
      for (let i = 0; i < attempts; i++) {
        // => Retry loop
        try {
          return await originalMethod.apply(this, args); // => Try original method
        } catch (error) {
          if (i === attempts - 1) throw error; // => Rethrow on last attempt
          console.log(`Retry ${i + 1}/${attempts}`);
        }
      }
    };
  };
}

class ApiClient {
  @Log
  @Retry(3)
  async fetchData(url: string): Promise<string> {
    // => Multiple decorators
    console.log(`Fetching ${url}`);
    if (Math.random() > 0.7) throw new Error("Network error"); // => Simulate failure
    return "data";
  }
}

Key Takeaway: Decorators add metadata and modify behavior declaratively. Compose multiple decorators for layered functionality. Use reflect-metadata for parameter validation.

Why It Matters: Decorators power dependency injection, route handlers, entity definitions, and validation. Essential for framework-level declarative programming. Enables clean separation of concerns.

Example 64: Symbol Usage for Unique Properties

Symbols create unique property keys that don’t conflict with string properties. Use for meta-programming and private-like properties.

// BASIC SYMBOL
const id = Symbol("id"); // => Create unique symbol
const id2 = Symbol("id"); // => Different symbol (same description)

console.log(id === id2); // => false (symbols always unique)

const user = {
  name: "Alice",
  [id]: 123, // => Symbol as property key
};

console.log(user[id]); // => 123
console.log(user.name); // => "Alice"

// SYMBOLS DON'T APPEAR IN NORMAL ITERATION
console.log(Object.keys(user)); // => ["name"] (symbol key omitted)
console.log(Object.getOwnPropertyNames(user)); // => ["name"]
console.log(Object.getOwnPropertySymbols(user)); // => [Symbol(id)] (only way to get symbols)

// WELL-KNOWN SYMBOLS - ITERATOR
class NumberRange {
  constructor(
    private start: number,
    private end: number,
  ) {}

  [Symbol.iterator]() {
    // => Make iterable
    let current = this.start;
    const end = this.end;

    return {
      next() {
        if (current <= end) {
          return { value: current++, done: false }; // => Yield value
        }
        return { value: undefined, done: true }; // => Iteration complete
      },
    };
  }
}

const range = new NumberRange(1, 5);
for (const num of range) {
  // => Uses Symbol.iterator
  console.log(num); // => 1, 2, 3, 4, 5
}

// WELL-KNOWN SYMBOLS - TO PRIMITIVE
class Money {
  constructor(private amount: number) {}

  [Symbol.toPrimitive](hint: string) {
    // => Custom type coercion
    if (hint === "number") {
      return this.amount; // => Return number
    }
    if (hint === "string") {
      return `$${this.amount}`; // => Return string
    }
    return this.amount; // => Default
  }
}

const money = new Money(100);
console.log(+money); // => 100 (number coercion)
console.log(`Total: ${money}`); // => "Total: $100" (string coercion)
console.log(money + 50); // => 150 (default coercion)

// SYMBOL AS METADATA KEY
const metadataKey = Symbol("metadata");

function addMetadata(target: any, data: any) {
  target[metadataKey] = data; // => Store metadata
}

class Product {
  name: string;
}

addMetadata(Product, { version: "1.0", author: "Alice" });
console.log((Product as any)[metadataKey]); // => { version: "1.0", author: "Alice" }

// GLOBAL SYMBOL REGISTRY
const globalId = Symbol.for("app.userId"); // => Create/retrieve global symbol
const sameGlobalId = Symbol.for("app.userId"); // => Retrieves same symbol

console.log(globalId === sameGlobalId); // => true (shared global symbol)
console.log(Symbol.keyFor(globalId)); // => "app.userId" (get key from symbol)

// SYMBOL-BASED PRIVATE PROPERTIES
const _balance = Symbol("balance");

class BankAccount {
  [_balance]: number; // => Symbol property

  constructor(initialBalance: number) {
    this[_balance] = initialBalance; // => Initialize private-like property
  }

  deposit(amount: number) {
    this[_balance] += amount; // => Modify private-like property
  }

  getBalance(): number {
    return this[_balance]; // => Access private-like property
  }
}

const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // => 1500
console.log(Object.keys(account)); // => [] (symbol property hidden)

Key Takeaway: Symbols create unique property keys. Well-known symbols customize language behavior (Symbol.iterator, Symbol.toPrimitive). Use Symbol.for() for global shared symbols.

Why It Matters: Symbols power custom iterators, JSON serialization control, and private-like properties before private fields. Frameworks can use symbols for element identification and observable interop. Essential for meta-programming and protocol implementation.

Example 65: AsyncIterator and AsyncGenerator Types

AsyncIterators enable iteration over async sequences. AsyncGenerators simplify async iterator creation.

// ASYNC ITERATOR INTERFACE
interface AsyncIterator<T> {
  next(): Promise<IteratorResult<T>>; // => Returns promise of result
}

interface AsyncIterable<T> {
  [Symbol.asyncIterator](): AsyncIterator<T>; // => Returns async iterator
}

// MANUAL ASYNC ITERATOR
class AsyncNumberRange implements AsyncIterable<number> {
  constructor(
    private start: number,
    private end: number,
    private delay: number,
  ) {}

  [Symbol.asyncIterator](): AsyncIterator<number> {
    let current = this.start;
    const end = this.end;
    const delay = this.delay;

    return {
      async next(): Promise<IteratorResult<number>> {
        // => Async next method
        if (current <= end) {
          await new Promise((resolve) => setTimeout(resolve, delay)); // => Simulate async operation
          return { value: current++, done: false }; // => Yield value
        }
        return { value: undefined, done: true }; // => Complete
      },
    };
  }
}

// USAGE WITH FOR-AWAIT-OF
async function consumeAsyncRange() {
  const range = new AsyncNumberRange(1, 5, 100); // => 1-5 with 100ms delay

  for await (const num of range) {
    // => Async iteration
    console.log(num); // => 1, 2, 3, 4, 5 (with delays)
  }
}

// ASYNC GENERATOR FUNCTION
async function* asyncNumberGenerator(start: number, end: number) {
  for (let i = start; i <= end; i++) {
    await new Promise((resolve) => setTimeout(resolve, 100)); // => Delay
    yield i; // => Yield value asynchronously
  }
}

async function useAsyncGenerator() {
  for await (const num of asyncNumberGenerator(1, 5)) {
    console.log(num); // => 1, 2, 3, 4, 5 (with delays)
  }
}

// ASYNC GENERATOR WITH API PAGINATION
async function* fetchPages<T>(baseUrl: string, pageSize: number): AsyncGenerator<T[], void, undefined> {
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const response = await fetch(`${baseUrl}?page=${page}&size=${pageSize}`); // => Fetch page
    const data: T[] = await response.json(); // => Parse JSON

    if (data.length < pageSize) {
      hasMore = false; // => Last page
    }

    yield data; // => Yield page data
    page++;
  }
}

async function processAllUsers() {
  for await (const users of fetchPages<{
    id: string;
    name: string;
  }>("https://api.example.com/users", 100)) {
    console.log(`Processing ${users.length} users`); // => Process each page
  }
}

// ASYNC ITERATOR HELPERS
async function* map<T, U>(source: AsyncIterable<T>, transform: (item: T) => U): AsyncGenerator<U, void, undefined> {
  for await (const item of source) {
    // => Iterate source
    yield transform(item); // => Transform and yield
  }
}

async function* filter<T>(
  source: AsyncIterable<T>,
  predicate: (item: T) => boolean,
): AsyncGenerator<T, void, undefined> {
  for await (const item of source) {
    if (predicate(item)) {
      // => Check predicate
      yield item; // => Yield if passes
    }
  }
}

// COMPOSE ASYNC ITERATORS
async function processData() {
  const numbers = asyncNumberGenerator(1, 10);
  const doubled = map(numbers, (n) => n * 2); // => Transform
  const filtered = filter(doubled, (n) => n > 10); // => Filter

  for await (const num of filtered) {
    console.log(num); // => 12, 14, 16, 18, 20
  }
}

// ASYNC ITERATOR WITH ERROR HANDLING
async function* fetchWithRetry(urls: string[]): AsyncGenerator<string, void, undefined> {
  for (const url of urls) {
    let attempts = 0;
    const maxAttempts = 3;

    while (attempts < maxAttempts) {
      try {
        const response = await fetch(url); // => Attempt fetch
        const data = await response.text();
        yield data; // => Yield on success
        break; // => Exit retry loop
      } catch (error) {
        attempts++;
        if (attempts >= maxAttempts) {
          console.error(`Failed to fetch ${url} after ${maxAttempts} attempts`);
          yield `ERROR: ${url}`; // => Yield error marker
        }
      }
    }
  }
}

Key Takeaway: AsyncIterators handle async sequences with for-await-of loops. AsyncGenerators simplify async iterator creation with yield. Compose async iterators for data pipelines.

Why It Matters: AsyncIterators power database cursor iteration, API pagination, real-time data streams, and file processing. Node.js streams implement async iterators. Essential for handling large async datasets without loading everything into memory.

Example 66: WeakMap and WeakSet Typing

WeakMaps and WeakSets hold weak object references. Objects are garbage collected when no strong references exist.

// WEAKMAP FOR PRIVATE DATA
const privateData = new WeakMap<object, { secret: string }>(); // => WeakMap with typed values

class User {
  constructor(name: string) {
    privateData.set(this, { secret: `${name}-secret-key` }); // => Store private data
  }

  getSecret(): string {
    const data = privateData.get(this); // => Retrieve private data
    return data ? data.secret : ""; // => Type-safe access
  }
}

const user1 = new User("Alice");
console.log(user1.getSecret()); // => "Alice-secret-key"
// No way to access privateData from outside without User instance

// WEAKMAP FOR METADATA
const metadata = new WeakMap<Function, { version: string; author: string }>(); // => Function metadata

function addMetadata(target: Function, meta: { version: string; author: string }) {
  metadata.set(target, meta); // => Associate metadata with function
}

function Component() {}

addMetadata(Component, { version: "1.0", author: "Alice" });
console.log(metadata.get(Component)); // => { version: "1.0", author: "Alice" }

// WEAKSET FOR OBJECT TRACKING
const processedObjects = new WeakSet<object>(); // => Track processed objects

function processOnce<T extends object>(obj: T, processor: (obj: T) => void) {
  if (processedObjects.has(obj)) {
    // => Check if already processed
    console.log("Already processed");
    return;
  }

  processor(obj); // => Process object
  processedObjects.add(obj); // => Mark as processed
}

const data = { value: 42 };
processOnce(data, (obj) => console.log("Processing", obj)); // => "Processing { value: 42 }"
processOnce(data, (obj) => console.log("Processing", obj)); // => "Already processed"

// WEAKMAP FOR DOM ELEMENT DATA
const elementData = new WeakMap<HTMLElement, { clicks: number; lastClick: Date }>(); // => DOM element metadata

function trackClicks(element: HTMLElement) {
  const data = elementData.get(element) || { clicks: 0, lastClick: new Date() }; // => Get or initialize
  data.clicks++; // => Increment clicks
  data.lastClick = new Date(); // => Update timestamp
  elementData.set(element, data); // => Update metadata
}

// When element removed from DOM, metadata is garbage collected automatically

// WEAKMAP FOR MEMOIZATION
const memoCache = new WeakMap<object, any>(); // => Cache for objects

function memoize<T extends object, R>(fn: (obj: T) => R): (obj: T) => R {
  return (obj: T): R => {
    if (memoCache.has(obj)) {
      // => Check cache
      return memoCache.get(obj); // => Return cached result
    }

    const result = fn(obj); // => Compute result
    memoCache.set(obj, result); // => Cache result
    return result;
  };
}

const expensiveComputation = memoize((obj: { value: number }) => {
  console.log("Computing...");
  return obj.value * 2;
});

const obj = { value: 5 };
console.log(expensiveComputation(obj)); // => "Computing..." then 10
console.log(expensiveComputation(obj)); // => 10 (cached, no "Computing...")

// WEAKMAP VS MAP MEMORY BEHAVIOR
const strongMap = new Map<object, string>(); // => Strong references
const weakMap = new WeakMap<object, string>(); // => Weak references

let obj1 = { id: 1 };
let obj2 = { id: 2 };

strongMap.set(obj1, "value1"); // => Strong reference prevents GC
weakMap.set(obj2, "value2"); // => Weak reference allows GC

obj1 = null as any; // => Remove strong reference, but Map still holds it
obj2 = null as any; // => Remove strong reference, WeakMap allows GC

// obj1 NOT garbage collected (Map holds reference)
// obj2 IS garbage collected (WeakMap doesn't prevent GC)

Key Takeaway: WeakMaps and WeakSets hold weak references allowing garbage collection. Use WeakMaps for private data without preventing object cleanup. WeakSets track objects without memory leaks.

Why It Matters: WeakMaps power private class data before private fields, DOM element metadata without memory leaks, and object memoization. Frameworks can use WeakMaps for reconciliation. Essential for preventing memory leaks in long-running applications.

Example 67: Proxy Typing for Meta-Programming

Proxies intercept object operations. Type proxies carefully to maintain type safety while adding dynamic behavior.

// BASIC PROXY WITH TYPED HANDLER
interface User {
  name: string;
  age: number;
}

const userHandler: ProxyHandler<User> = {
  get(target: User, prop: string | symbol, receiver: any): any {
    // => Intercept property access
    console.log(`Getting ${String(prop)}`);
    return Reflect.get(target, prop, receiver); // => Forward to target
  },

  set(target: User, prop: string | symbol, value: any, receiver: any): boolean {
    // => Intercept property assignment
    console.log(`Setting ${String(prop)} to ${value}`);
    return Reflect.set(target, prop, value, receiver); // => Forward to target
  },
};

const user: User = { name: "Alice", age: 30 };
const proxiedUser = new Proxy(user, userHandler);

console.log(proxiedUser.name); // => "Getting name" then "Alice"
proxiedUser.age = 31; // => "Setting age to 31"

// VALIDATION PROXY
function createValidatedObject<T extends object>(
  obj: T,
  validators: Partial<Record<keyof T, (value: any) => boolean>>,
): T {
  return new Proxy(obj, {
    set(target: T, prop: string | symbol, value: any): boolean {
      const key = prop as keyof T;
      const validator = validators[key]; // => Get validator for property

      if (validator && !validator(value)) {
        // => Validate if validator exists
        throw new Error(`Invalid value for ${String(prop)}: ${value}`);
      }

      return Reflect.set(target, prop, value); // => Set if valid
    },
  });
}

const validatedUser = createValidatedObject(
  { name: "Alice", age: 30 },
  {
    age: (value) => typeof value === "number" && value > 0, // => Age validator
    name: (value) => typeof value === "string" && value.length > 0, // => Name validator
  },
);

validatedUser.age = 25; // => Valid
// validatedUser.age = -5; // => Throws: Invalid value for age: -5

// OBSERVABLE PROXY
type Observer<T> = (prop: keyof T, oldValue: any, newValue: any) => void;

function observable<T extends object>(obj: T, observer: Observer<T>): T {
  return new Proxy(obj, {
    set(target: T, prop: string | symbol, value: any): boolean {
      const key = prop as keyof T;
      const oldValue = target[key]; // => Store old value

      const result = Reflect.set(target, prop, value); // => Set new value

      if (oldValue !== value) {
        // => Notify only if changed
        observer(key, oldValue, value); // => Call observer
      }

      return result;
    },
  });
}

const observableUser = observable({ name: "Alice", age: 30 }, (prop, oldVal, newVal) => {
  console.log(`${String(prop)} changed from ${oldVal} to ${newVal}`);
});

observableUser.age = 31; // => "age changed from 30 to 31"

// DEFAULT VALUE PROXY
function withDefaults<T extends object>(obj: T, defaults: Partial<T>): T {
  return new Proxy(obj, {
    get(target: T, prop: string | symbol): any {
      const key = prop as keyof T;
      return target[key] !== undefined ? target[key] : defaults[key]; // => Use default if undefined
    },
  });
}

const config = withDefaults({ port: 3000 }, { host: "localhost", port: 8080, debug: false });

console.log(config.port); // => 3000 (from object)
console.log((config as any).host); // => "localhost" (from defaults)
console.log((config as any).debug); // => false (from defaults)

// METHOD CALL TRACKING PROXY
function trackMethodCalls<T extends object>(obj: T): T {
  const callCounts = new Map<string, number>(); // => Track call counts

  return new Proxy(obj, {
    get(target: T, prop: string | symbol): any {
      const value = Reflect.get(target, prop);

      if (typeof value === "function") {
        // => Intercept methods only
        return function (...args: any[]) {
          const key = String(prop);
          callCounts.set(key, (callCounts.get(key) || 0) + 1); // => Increment count
          console.log(`${key} called ${callCounts.get(key)} times`);
          return value.apply(target, args); // => Call original method
        };
      }

      return value;
    },
  });
}

class Calculator {
  add(a: number, b: number): number {
    return a + b;
  }
  multiply(a: number, b: number): number {
    return a * b;
  }
}

const trackedCalc = trackMethodCalls(new Calculator());
trackedCalc.add(1, 2); // => "add called 1 times"
trackedCalc.add(3, 4); // => "add called 2 times"
trackedCalc.multiply(2, 3); // => "multiply called 1 times"

// NEGATIVE ARRAY INDEXING PROXY
function createArrayProxy<T>(arr: T[]): T[] {
  return new Proxy(arr, {
    get(target: T[], prop: string | symbol): any {
      if (typeof prop === "string") {
        const index = parseInt(prop, 10);
        if (!isNaN(index) && index < 0) {
          // => Negative index
          return target[target.length + index]; // => Convert to positive
        }
      }
      return Reflect.get(target, prop);
    },
  });
}

const arr = createArrayProxy([1, 2, 3, 4, 5]);
console.log(arr[-1]); // => 5 (last element)
console.log(arr[-2]); // => 4 (second to last)

Key Takeaway: Proxies intercept object operations (get, set, has, etc.). Use ProxyHandler for typed handlers. Combine with Reflect API for proper forwarding.

Why It Matters: Proxies power reactivity systems, validation libraries, observable patterns, and metaprogramming frameworks. State management can use proxies for immutable updates. Essential for framework-level transparent interception.

Example 68: Reflect API for Meta-Programming

Reflect API provides methods for interceptable JavaScript operations. Use with Proxies for complete meta-programming control.

// REFLECT.GET WITH DEFAULT VALUE
const obj = { name: "Alice", age: 30 };

const name = Reflect.get(obj, "name"); // => "Alice" (normal get)
const missing = Reflect.get(obj, "missing"); // => undefined

// REFLECT.SET WITH VALIDATION
function safeSet<T extends object>(target: T, prop: keyof T, value: any): boolean {
  if (typeof value === "undefined") {
    // => Prevent setting undefined
    console.log("Cannot set undefined value");
    return false;
  }

  return Reflect.set(target, prop, value); // => Set if valid
}

const user = { name: "Alice", age: 30 };
safeSet(user, "age", 31); // => true, user.age = 31
safeSet(user, "age", undefined); // => false, "Cannot set undefined value"

// REFLECT.HAS FOR PROPERTY EXISTENCE
console.log(Reflect.has(user, "name")); // => true
console.log(Reflect.has(user, "missing")); // => false
console.log("name" in user); // => true (equivalent to Reflect.has)

// REFLECT.DELETEPROPERTY
const temp = { value: 42, temp: true };
Reflect.deleteProperty(temp, "temp"); // => Deletes property
console.log(temp); // => { value: 42 }

// REFLECT.OWNKEYS (ALL PROPERTY KEYS)
const symbolKey = Symbol("secret");
const mixed = {
  name: "Alice",
  age: 30,
  [symbolKey]: "hidden",
};

console.log(Object.keys(mixed)); // => ["name", "age"] (only strings)
console.log(Reflect.ownKeys(mixed)); // => ["name", "age", Symbol(secret)] (strings + symbols)

// REFLECT.CONSTRUCT FOR DYNAMIC INSTANTIATION
class Person {
  constructor(
    public name: string,
    public age: number,
  ) {}
}

const person = Reflect.construct(Person, ["Alice", 30]); // => new Person("Alice", 30)
console.log(person); // => Person { name: "Alice", age: 30 }

// REFLECT.GETPROTOTYPEOF
const proto = Reflect.getPrototypeOf(person); // => Person.prototype
console.log(proto === Person.prototype); // => true

// REFLECT.SETPROTOTYPEOF
const animal = { species: "dog" };
const dog = { name: "Rex" };
Reflect.setPrototypeOf(dog, animal); // => Set prototype
console.log((dog as any).species); // => "dog" (inherited)

// REFLECT.DEFINEPROPERTY (PROPERTY DESCRIPTOR)
const config = {};
Reflect.defineProperty(config, "apiUrl", {
  value: "https://api.example.com",
  writable: false, // => Read-only
  enumerable: true,
  configurable: false,
});

console.log((config as any).apiUrl); // => "https://api.example.com"
// (config as any).apiUrl = "new-url"; // => Error: Cannot assign to read-only property

// REFLECT.GETOWNPROPERTYDESCRIPTOR
const descriptor = Reflect.getOwnPropertyDescriptor(config, "apiUrl");
console.log(descriptor); // => { value: "...", writable: false, enumerable: true, configurable: false }

// REFLECT.APPLY FOR FUNCTION CALLS
function greet(this: { name: string }, greeting: string): string {
  return `${greeting}, ${this.name}!`;
}

const context = { name: "Alice" };
const result = Reflect.apply(greet, context, ["Hello"]); // => Call with context
console.log(result); // => "Hello, Alice!"

// REFLECT WITH PROXY FOR LOGGING
function createLoggingProxy<T extends object>(target: T, label: string): T {
  return new Proxy(target, {
    get(target: T, prop: string | symbol, receiver: any): any {
      console.log(`[${label}] GET ${String(prop)}`);
      return Reflect.get(target, prop, receiver); // => Use Reflect for proper behavior
    },

    set(target: T, prop: string | symbol, value: any, receiver: any): boolean {
      console.log(`[${label}] SET ${String(prop)} = ${value}`);
      return Reflect.set(target, prop, value, receiver); // => Use Reflect for proper behavior
    },

    has(target: T, prop: string | symbol): boolean {
      console.log(`[${label}] HAS ${String(prop)}`);
      return Reflect.has(target, prop);
    },

    deleteProperty(target: T, prop: string | symbol): boolean {
      console.log(`[${label}] DELETE ${String(prop)}`);
      return Reflect.deleteProperty(target, prop);
    },
  });
}

const loggedUser = createLoggingProxy({ name: "Alice", age: 30 }, "User");
console.log(loggedUser.name); // => "[User] GET name" then "Alice"
loggedUser.age = 31; // => "[User] SET age = 31"
console.log("name" in loggedUser); // => "[User] HAS name" then true

Key Takeaway: Reflect API provides methods for all interceptable operations. Use with Proxies for proper forwarding. Reflect.ownKeys includes both string and symbol keys.

Why It Matters: Reflect API powers framework-level object manipulation, serialization libraries, and validation frameworks. ORMs can use Reflect for metadata reflection. Essential for building robust Proxy handlers and metaprogramming utilities.

Example 69: Assertion Functions for Type Narrowing

Assertion functions assert conditions and narrow types in the calling scope. Use for validation and error handling.

// BASIC ASSERTION FUNCTION
function assert(condition: any, message?: string): asserts condition {
  // => asserts signature
  if (!condition) {
    throw new Error(message || "Assertion failed"); // => Throw if condition false
  }
}

function processValue(value: string | null) {
  assert(value !== null, "Value must not be null"); // => Assert non-null
  // After assertion, value is typed as string (not string | null)
  console.log(value.toUpperCase()); // => Safe to call string methods
}

// TYPE PREDICATE ASSERTION
function assertIsString(value: any): asserts value is string {
  // => asserts type predicate
  if (typeof value !== "string") {
    throw new Error("Value must be string");
  }
}

function handleValue(value: unknown) {
  assertIsString(value); // => Assert string type
  // After assertion, value is typed as string
  console.log(value.toUpperCase()); // => Type-safe string operation
}

// ASSERT NON-NULL
function assertNonNull<T>(value: T | null | undefined, message?: string): asserts value is T {
  if (value === null || value === undefined) {
    throw new Error(message || "Value is null or undefined");
  }
}

function getUser(id: string): { name: string } | null {
  return null; // => Placeholder
}

function processUser(id: string) {
  const user = getUser(id); // => Type: { name: string } | null
  assertNonNull(user, "User not found"); // => Assert non-null
  // After assertion, user is typed as { name: string }
  console.log(user.name); // => Safe access
}

// ASSERT ARRAY NON-EMPTY
function assertNonEmpty<T>(arr: T[]): asserts arr is [T, ...T[]] {
  // => Assert at least one element
  if (arr.length === 0) {
    throw new Error("Array must not be empty");
  }
}

function processArray(arr: number[]) {
  assertNonEmpty(arr); // => Assert non-empty
  // After assertion, arr is typed as [number, ...number[]]
  const first = arr[0]; // => Type: number (not number | undefined)
  console.log(first.toFixed(2)); // => Safe to use without null check
}

// ASSERT OBJECT PROPERTY EXISTS
function assertHasProperty<T, K extends string>(obj: T, prop: K, message?: string): asserts obj is T & Record<K, any> {
  if (!(prop in obj)) {
    throw new Error(message || `Property ${prop} does not exist`);
  }
}

function processConfig(config: object) {
  assertHasProperty(config, "apiUrl"); // => Assert property exists
  // After assertion, config typed as object & Record<"apiUrl", any>
  console.log((config as any).apiUrl); // => Type-safe access
}

// ASSERT UNION TYPE NARROWING
type Success = { status: "success"; data: string };
type Error = { status: "error"; message: string };
type Result = Success | Error;

function assertSuccess(result: Result): asserts result is Success {
  if (result.status !== "success") {
    throw new Error("Result is not success");
  }
}

function handleResult(result: Result) {
  assertSuccess(result); // => Assert success type
  // After assertion, result is typed as Success
  console.log(result.data); // => Type-safe access to data property
}

// ASSERTION WITH VALIDATION
interface User {
  name: string;
  email: string;
  age: number;
}

function assertValidUser(user: any): asserts user is User {
  if (typeof user !== "object" || user === null) {
    throw new Error("User must be object");
  }

  if (typeof user.name !== "string" || user.name.length === 0) {
    throw new Error("User name must be non-empty string");
  }

  if (typeof user.email !== "string" || !user.email.includes("@")) {
    throw new Error("User email must be valid");
  }

  if (typeof user.age !== "number" || user.age < 0) {
    throw new Error("User age must be positive number");
  }
}

function createUser(data: unknown) {
  assertValidUser(data); // => Assert and validate
  // After assertion, data is typed as User
  return data; // => Type: User
}

Key Takeaway: Assertion functions use asserts signature to narrow types. Throw errors on failed assertions. Types are narrowed in calling scope after assertion.

Why It Matters: Assertion functions power validation libraries, type guards, and error handling. Zod uses assertions for schema validation. Essential for runtime validation with compile-time type narrowing. Cleaner than type predicates with if statements.

Example 70: Advanced Type Predicates with Guards

Type predicates enable custom type guards. Use for complex runtime type checking with type narrowing.

// BASIC TYPE PREDICATE
function isString(value: any): value is string {
  // => Type predicate
  return typeof value === "string";
}

function handleValue(value: string | number) {
  if (isString(value)) {
    // => Type guard
    console.log(value.toUpperCase()); // => value typed as string
  } else {
    console.log(value.toFixed(2)); // => value typed as number
  }
}

// OBJECT TYPE GUARD
interface User {
  name: string;
  email: string;
}

interface Admin {
  name: string;
  email: string;
  permissions: string[];
}

function isAdmin(user: User | Admin): user is Admin {
  return "permissions" in user; // => Check discriminating property
}

function greetUser(user: User | Admin) {
  if (isAdmin(user)) {
    console.log(`Admin ${user.name} with ${user.permissions.length} permissions`); // => user typed as Admin
  } else {
    console.log(`User ${user.name}`); // => user typed as User
  }
}

// ARRAY TYPE GUARD
function isStringArray(value: any): value is string[] {
  return Array.isArray(value) && value.every((item) => typeof item === "string"); // => Check all elements
}

function processData(data: unknown) {
  if (isStringArray(data)) {
    data.forEach((str) => console.log(str.toUpperCase())); // => data typed as string[]
  }
}

// NULLABLE TYPE GUARD
function isDefined<T>(value: T | null | undefined): value is T {
  return value !== null && value !== undefined; // => Non-null check
}

const values: (number | null | undefined)[] = [1, null, 2, undefined, 3];
const definedValues = values.filter(isDefined); // => Type: number[] (filtered)

// DISCRIMINATED UNION TYPE GUARD
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rectangle"; width: number; height: number }
  | { kind: "triangle"; base: number; height: number };

function isCircle(shape: Shape): shape is { kind: "circle"; radius: number } {
  return shape.kind === "circle"; // => Check discriminant
}

function getArea(shape: Shape): number {
  if (isCircle(shape)) {
    return Math.PI * shape.radius ** 2; // => shape typed as circle
  } else if (shape.kind === "rectangle") {
    return shape.width * shape.height; // => shape typed as rectangle
  } else {
    return (shape.base * shape.height) / 2; // => shape typed as triangle
  }
}

// GENERIC TYPE GUARD
function isArrayOf<T>(value: any, guard: (item: any) => item is T): value is T[] {
  return Array.isArray(value) && value.every(guard); // => Apply guard to all elements
}

function isNumber(value: any): value is number {
  return typeof value === "number";
}

const data: unknown = [1, 2, 3];
if (isArrayOf(data, isNumber)) {
  data.forEach((num) => console.log(num.toFixed(2))); // => data typed as number[]
}

// ERROR TYPE GUARD
function isError(error: any): error is Error {
  return error instanceof Error; // => Check Error instance
}

try {
  throw new Error("Something went wrong");
} catch (error) {
  if (isError(error)) {
    console.log(error.message); // => error typed as Error
  }
}

// PROMISE TYPE GUARD
function isPromise<T>(value: any): value is Promise<T> {
  return value && typeof value.then === "function"; // => Check thenable
}

async function handleAsync(value: string | Promise<string>) {
  if (isPromise(value)) {
    const result = await value; // => value typed as Promise<string>
    console.log(result);
  } else {
    console.log(value); // => value typed as string
  }
}

Key Takeaway: Type predicates use value is Type signature for custom type guards. Narrow types based on runtime checks. Combine with generics for reusable guards.

Why It Matters: Type predicates power type-safe filtering, validation, and error handling. RxJS uses type predicates for operator typing. Essential for bridging runtime checks and compile-time types. Cleaner than type assertions.

Example 71: Variance Annotations (in/out)

Variance annotations (in for contravariance, out for covariance) control how types relate in generic positions. TypeScript 4.7+ supports explicit variance.

// COVARIANT (OUT) - TYPE FLOWS OUTWARD
interface Producer<out T> {
  // => T only in output positions
  produce(): T; // => Returns T
  // consume(value: T): void; // => Error: 'out' parameter used in input position
}

// Covariant types are assignable from subtypes to supertypes
class Animal {
  name: string = "";
}
class Dog extends Animal {
  bark(): void {}
}

const dogProducer: Producer<Dog> = {
  produce: () => new Dog(),
};

const animalProducer: Producer<Animal> = dogProducer; // => Valid: Producer<Dog> assignable to Producer<Animal>
const animal = animalProducer.produce(); // => Type: Animal, runtime: Dog

// CONTRAVARIANT (IN) - TYPE FLOWS INWARD
interface Consumer<in T> {
  // => T only in input positions
  consume(value: T): void; // => Accepts T
  // produce(): T; // => Error: 'in' parameter used in output position
}

const animalConsumer: Consumer<Animal> = {
  consume: (animal: Animal) => console.log(animal.name),
};

const dogConsumer: Consumer<Dog> = animalConsumer; // => Valid: Consumer<Animal> assignable to Consumer<Dog>
dogConsumer.consume(new Dog()); // => Passes Dog where Animal expected (widening is safe)

// INVARIANT (NO ANNOTATION) - TYPE MUST MATCH EXACTLY
interface Storage<T> {
  // => T in both input and output positions
  get(): T;
  set(value: T): void;
}

const dogStorage: Storage<Dog> = {
  get: () => new Dog(),
  set: (dog: Dog) => {},
};

// const animalStorage: Storage<Animal> = dogStorage; // => Error: Storage is invariant
// const specificStorage: Storage<Dog> = animalStorage; // => Error: Storage is invariant

// PRACTICAL USE CASE - READONLY ARRAY
interface ReadonlyArrayLike<out T> {
  // => Covariant: T only in output positions
  readonly length: number;
  item(index: number): T | undefined;
}

const dogs: ReadonlyArrayLike<Dog> = {
  length: 2,
  item: (i) => (i === 0 ? new Dog() : undefined),
};

const animals: ReadonlyArrayLike<Animal> = dogs; // => Valid: covariant assignment

// PRACTICAL USE CASE - COMPARATOR
interface Comparator<in T> {
  // => Contravariant: T only in input position
  compare(a: T, b: T): number;
}

const animalComparator: Comparator<Animal> = {
  compare: (a, b) => a.name.localeCompare(b.name),
};

const dogComparator: Comparator<Dog> = animalComparator; // => Valid: contravariant assignment

// MIXED VARIANCE
interface Transformer<in TIn, out TOut> {
  // => TIn contravariant, TOut covariant
  transform(input: TIn): TOut;
}

const animalToDog: Transformer<Animal, Dog> = {
  transform: (animal) => new Dog(),
};

const dogToAnimal: Transformer<Dog, Animal> = animalToDog; // => Valid: wider input, narrower output

Key Takeaway: Use out for covariant parameters (output-only). Use in for contravariant parameters (input-only). No annotation means invariant (both input and output).

Why It Matters: Variance annotations make type relationships explicit, catching incorrect assignments. Components can use covariance for props (Producer-like). Event handlers use contravariance (Consumer-like). Essential for type-safe callback and container types.

Example 72: Performance Optimization - Type Complexity

Complex types slow compilation. Optimize type performance for large codebases.

// PROBLEM: DEEPLY NESTED CONDITIONAL TYPES
type BadDeepPartial<T> = {
  [K in keyof T]: T[K] extends object ? BadDeepPartial<T[K]> : T[K]; // => Recursive without depth limit
};

// Large objects cause exponential type checking time
type SlowType = BadDeepPartial<{
  a: { b: { c: { d: { e: { f: string } } } } }; // => Deep nesting
  x: { y: { z: number } };
}>;

// SOLUTION: LIMIT RECURSION DEPTH
type DeepPartial<T, D extends number = 5> = D extends 0 // => Depth counter
  ? T // => Base case: stop at depth 0
  : {
      [K in keyof T]?: T[K] extends object ? DeepPartial<T[K], Prev<D>> : T[K]; // => Decrement depth
    };

type Prev<N extends number> = [-1, 0, 1, 2, 3, 4, 5][N]; // => Decrement lookup

type FastType = DeepPartial<{
  // => Limited depth
  a: { b: { c: { d: { e: { f: string } } } } };
  x: { y: { z: number } };
}>;

// PROBLEM: EXCESSIVE UNION EXPANSION
type BadPermutations<T extends string[]> = T extends [infer First extends string, ...infer Rest extends string[]]
  ? `${First}-${BadPermutations<Rest>}` | BadPermutations<Rest> // => Exponential growth
  : "";

// Large arrays cause type instantiation limit errors
// type SlowPermutations = BadPermutations<["a", "b", "c", "d", "e", "f", "g"]>;

// SOLUTION: AVOID EXCESSIVE UNIONS
type JoinWith<T extends string[], Sep extends string = "-"> = T extends [
  infer First extends string,
  ...infer Rest extends string[],
]
  ? Rest extends []
    ? First // => Last element
    : `${First}${Sep}${JoinWith<Rest, Sep>}` // => Linear growth
  : "";

type FastJoin = JoinWith<["a", "b", "c", "d", "e", "f", "g"]>; // => "a-b-c-d-e-f-g"

// PROBLEM: REDUNDANT TYPE CALCULATIONS
type BadMappedType<T> = {
  [K in keyof T]: T[K] extends string ? Uppercase<T[K]> : T[K]; // => Recalculates for every K
};

// SOLUTION: EXTRACT HELPER TYPE
type UppercaseIfString<T> = T extends string ? Uppercase<T> : T; // => Reusable helper

type FastMappedType<T> = {
  [K in keyof T]: UppercaseIfString<T[K]>; // => Delegate to helper
};

// PROBLEM: COMPLEX INTERSECTION TYPES
type BadIntersection = { a: string } & { b: number } & { c: boolean } & { d: string[] } & { e: object };

// SOLUTION: FLATTEN INTERSECTION WITH HELPER
type Flatten<T> = {
  [K in keyof T]: T[K]; // => Flatten to single object type
};

type FlatIntersection = Flatten<BadIntersection>; // => Single object type

// BEST PRACTICES
// 1. Use type aliases to cache complex types
type UserId = string; // => Alias for reuse
type CachedUser = { id: UserId; name: string }; // => Reuse alias

// 2. Avoid deep nesting
type ShallowConfig = {
  api: { url: string; timeout: number }; // => Max 2-3 levels
  db: { host: string; port: number };
};

// 3. Use index signatures for dynamic keys
type DynamicConfig = {
  [key: string]: string | number; // => Single signature vs union of all keys
};

// 4. Prefer interfaces over type intersections
interface FastUser {
  // => Interface merging is faster
  id: string;
  name: string;
}

// 5. Use const assertions for literals
const config = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
} as const; // => Literal types without complex inference

Key Takeaway: Limit recursion depth to prevent exponential type checking. Avoid excessive union expansions. Extract reusable helper types. Flatten intersections when possible.

Why It Matters: Type complexity directly impacts IDE responsiveness and build times. Large projects with complex types can become unusable without optimization. Essential for maintaining developer productivity in enterprise codebases.

Example 73: UI Framework TypeScript Integration - Hooks and Components

Type UI components, hooks, and props for type-safe UI development.

import React, { useState, useEffect, useRef, useCallback, useMemo, useContext } from "react";

// FUNCTION COMPONENT WITH PROPS
interface ButtonProps {
  label: string;
  onClick: () => void;
  disabled?: boolean; // => Optional prop
  variant?: "primary" | "secondary"; // => Union type
}

const Button: React.FC<ButtonProps> = ({ label, onClick, disabled = false, variant = "primary" }) => {
  // => Destructured props with defaults
  return (
    <button onClick={onClick} disabled={disabled} className={variant}>
      {label}
    </button>
  );
};

// USESTATE WITH TYPE INFERENCE
function Counter() {
  const [count, setCount] = useState(0); // => Type inferred: number
  const [name, setName] = useState(""); // => Type inferred: string

  return (
    <div>
      <p>{name}</p>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

// USESTATE WITH EXPLICIT TYPE
interface User {
  id: string;
  name: string;
  email: string;
}

function UserProfile() {
  const [user, setUser] = useState<User | null>(null); // => Explicit nullable type

  useEffect(() => {
    // Fetch user data
    setUser({ id: "1", name: "Alice", email: "alice@example.com" });
  }, []);

  if (!user) return <div>Loading...</div>;

  return <div>{user.name}</div>; // => Type-safe access
}

// USEREF WITH DOM ELEMENTS
function TextInput() {
  const inputRef = useRef<HTMLInputElement>(null); // => DOM element ref

  const focusInput = () => {
    inputRef.current?.focus(); // => Optional chaining (ref might be null)
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus</button>
    </div>
  );
}

// USEREF FOR MUTABLE VALUE
function Timer() {
  const intervalRef = useRef<number | null>(null); // => Mutable value ref

  const startTimer = () => {
    intervalRef.current = window.setInterval(() => {
      // => Store interval ID
      console.log("Tick");
    }, 1000);
  };

  const stopTimer = () => {
    if (intervalRef.current !== null) {
      clearInterval(intervalRef.current); // => Clear with stored ID
      intervalRef.current = null;
    }
  };

  return (
    <div>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
    </div>
  );
}

// USECALLBACK WITH TYPED CALLBACK
interface TodoProps {
  todos: string[];
  onAdd: (todo: string) => void; // => Typed callback prop
}

const TodoList: React.FC<TodoProps> = ({ todos, onAdd }) => {
  const handleAdd = useCallback(() => {
    // => Memoized callback
    onAdd("New todo");
  }, [onAdd]); // => Dependency array

  return (
    <div>
      {todos.map((todo, i) => (
        <div key={i}>{todo}</div>
      ))}
      <button onClick={handleAdd}>Add</button>
    </div>
  );
};

// USEMEMO WITH TYPE INFERENCE
function ExpensiveComputation() {
  const [count, setCount] = useState(0);

  const doubled = useMemo(() => {
    // => Type inferred: number
    console.log("Computing...");
    return count * 2;
  }, [count]); // => Recompute when count changes

  return <div>{doubled}</div>;
}

// CUSTOM HOOK WITH GENERICS
function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T) => void] {
  // => Generic hook
  const [storedValue, setStoredValue] = useState<T>(() => {
    const item = window.localStorage.getItem(key);
    return item ? JSON.parse(item) : initialValue;
  });

  const setValue = (value: T) => {
    setStoredValue(value);
    window.localStorage.setItem(key, JSON.stringify(value));
  };

  return [storedValue, setValue]; // => Tuple return type
}

function Settings() {
  const [theme, setTheme] = useLocalStorage<"light" | "dark">("theme", "light"); // => Type-safe storage

  return (
    <div>
      <p>Theme: {theme}</p>
      <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>Toggle</button>
    </div>
  );
}

// USECONTEXT WITH TYPED CONTEXT
interface AuthContextType {
  user: User | null;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
}

const AuthContext = React.createContext<AuthContextType | undefined>(undefined); // => Typed context

function useAuth(): AuthContextType {
  // => Custom hook for context
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error("useAuth must be used within AuthProvider");
  }
  return context;
}

function LoginButton() {
  const { user, login, logout } = useAuth(); // => Type-safe context access

  return user ? <button onClick={logout}>Logout</button> : <button onClick={() => login("", "")}>Login</button>;
}

// CHILDREN PROP TYPE
interface CardProps {
  title: string;
  children: React.ReactNode; // => Accepts any renderable content
}

const Card: React.FC<CardProps> = ({ title, children }) => {
  return (
    <div>
      <h2>{title}</h2>
      <div>{children}</div>
    </div>
  );
};

// GENERIC COMPONENT
interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

function List<T>({ items, renderItem }: ListProps<T>) {
  // => Generic function component
  return <div>{items.map((item, i) => <div key={i}>{renderItem(item)}</div>)}</div>;
}

function UserList() {
  const users: User[] = [
    { id: "1", name: "Alice", email: "alice@example.com" },
    { id: "2", name: "Bob", email: "bob@example.com" },
  ];

  return <List items={users} renderItem={(user) => <div>{user.name}</div>} />; // => Type-inferred from items
}

Key Takeaway: Use React.FC<Props> for function components with props. Type hooks explicitly when inference insufficient. Custom hooks use generics for reusability. Context requires explicit typing.

Why It Matters: UI framework TypeScript integration catches prop errors, hook dependency issues, and invalid state updates at compile time. Essential for large applications with hundreds of components. Enables type-safe component development across different UI frameworks.

Example 74: Node.js Type Patterns - Streams and Events

Type Node.js streams, events, and async patterns for backend development.

import { EventEmitter } from "events";
import { Readable, Writable, Transform, pipeline } from "stream";
import { promisify } from "util";

// TYPED EVENT EMITTER
interface ServerEvents {
  request: [url: string, method: string]; // => Event payload tuple
  error: [error: Error];
  close: [];
}

class TypedEventEmitter<T extends Record<string, any[]>> {
  private emitter = new EventEmitter();

  on<K extends keyof T>(event: K, listener: (...args: T[K]) => void): this {
    // => Type-safe on
    this.emitter.on(event as string, listener);
    return this;
  }

  emit<K extends keyof T>(event: K, ...args: T[K]): boolean {
    // => Type-safe emit
    return this.emitter.emit(event as string, ...args);
  }

  off<K extends keyof T>(event: K, listener: (...args: T[K]) => void): this {
    // => Type-safe off
    this.emitter.off(event as string, listener);
    return this;
  }
}

const server = new TypedEventEmitter<ServerEvents>();

server.on("request", (url, method) => {
  // => Types inferred: string, string
  console.log(`${method} ${url}`);
});

server.emit("request", "/api/users", "GET"); // => Type-safe emit
// server.emit("request", 123, "GET"); // => Error: number not assignable to string

// TYPED READABLE STREAM
class NumberStream extends Readable {
  private current = 0;
  private max: number;

  constructor(max: number) {
    super({ objectMode: true }); // => Object mode for non-Buffer data
    this.max = max;
  }

  _read() {
    // => Required implementation
    if (this.current <= this.max) {
      this.push({ value: this.current++ }); // => Push data
    } else {
      this.push(null); // => Signal end
    }
  }
}

const numberStream = new NumberStream(5);
numberStream.on("data", (chunk: { value: number }) => {
  // => Type annotation needed
  console.log(chunk.value); // => 0, 1, 2, 3, 4, 5
});

// TYPED WRITABLE STREAM
class LogStream extends Writable {
  constructor() {
    super({ objectMode: true });
  }

  _write(chunk: any, encoding: string, callback: (error?: Error | null) => void) {
    // => Required implementation
    console.log("Received:", chunk);
    callback(); // => Signal completion
  }
}

// TYPED TRANSFORM STREAM
class DoubleTransform extends Transform {
  constructor() {
    super({ objectMode: true });
  }

  _transform(chunk: { value: number }, encoding: string, callback: (error?: Error | null, data?: any) => void) {
    // => Required implementation
    this.push({ value: chunk.value * 2 }); // => Transform and push
    callback(); // => Signal completion
  }
}

// PIPELINE WITH TYPED STREAMS
const pipelinePromise = promisify(pipeline); // => Promisified pipeline

async function processNumbers() {
  await pipelinePromise(
    new NumberStream(10), // => Source
    new DoubleTransform(), // => Transform
    new LogStream(), // => Destination
  );
  // => Type-safe pipeline composition
}

// TYPED BUFFER OPERATIONS
function processBuffer(buffer: Buffer): string {
  const str = buffer.toString("utf8"); // => Convert to string
  return str.toUpperCase(); // => Process
}

const buf = Buffer.from("hello", "utf8");
console.log(processBuffer(buf)); // => "HELLO"

// TYPED PROCESS ENV
declare global {
  namespace NodeJS {
    interface ProcessEnv {
      NODE_ENV: "development" | "production" | "test";
      PORT: string;
      DATABASE_URL: string;
    }
  }
}

const port = parseInt(process.env.PORT, 10); // => Type: string, parse to number
const env = process.env.NODE_ENV; // => Type: "development" | "production" | "test"

// ASYNC HOOKS TYPING
interface AsyncResource {
  type: string;
  triggerAsyncId: number;
}

// TYPED ERROR HANDLING
class AppError extends Error {
  constructor(
    public statusCode: number,
    message: string,
    public isOperational: boolean = true,
  ) {
    // => Custom error with properties
    super(message);
    Object.setPrototypeOf(this, AppError.prototype);
  }
}

function handleError(error: unknown): void {
  if (error instanceof AppError) {
    // => Type guard
    console.error(`[${error.statusCode}] ${error.message}`);
  } else if (error instanceof Error) {
    console.error(error.message);
  } else {
    console.error("Unknown error");
  }
}

Key Takeaway: Type Event Emitters with event map interfaces. Extend stream classes for custom streams. Use global augmentation for process.env typing. Create custom error classes for structured error handling.

Why It Matters: Node.js type patterns prevent runtime errors in backend services. Typed streams enable safe data pipeline composition. Event emitter typing catches incorrect event names. Essential for building reliable microservices and APIs.

Example 75: Express Middleware Typing

Type Express request handlers, middleware, and route parameters for type-safe web servers.

import express, { Request, Response, NextFunction, RequestHandler } from "express";

// EXTEND EXPRESS REQUEST
declare global {
  namespace Express {
    interface Request {
      user?: {
        // => Add user property
        id: string;
        email: string;
        role: "admin" | "user";
      };
      startTime?: number; // => Add timing property
    }
  }
}

// TYPED MIDDLEWARE
const authMiddleware: RequestHandler = (req: Request, res: Response, next: NextFunction) => {
  const token = req.headers.authorization; // => Get token

  if (!token) {
    res.status(401).json({ error: "Unauthorized" });
    return; // => Early return, no next() call
  }

  // Verify token (mocked)
  req.user = {
    // => Set typed user property
    id: "123",
    email: "alice@example.com",
    role: "admin",
  };

  next(); // => Continue to next middleware
};

const timingMiddleware: RequestHandler = (req: Request, res: Response, next: NextFunction) => {
  req.startTime = Date.now(); // => Record start time

  res.on("finish", () => {
    // => Hook into response finish
    const duration = Date.now() - (req.startTime || 0);
    console.log(`Request took ${duration}ms`);
  });

  next();
};

// TYPED ROUTE PARAMETERS
interface UserParams {
  userId: string;
}

interface CreateUserBody {
  name: string;
  email: string;
}

// REQUEST WITH GENERICS
type TypedRequest<P = {}, B = {}, Q = {}> = Request<P, any, B, Q>;

const app = express();

app.use(express.json());
app.use(timingMiddleware);

// TYPED ROUTE HANDLER
app.get("/users/:userId", authMiddleware, (req: TypedRequest<UserParams>, res: Response) => {
  // => Typed params
  const { userId } = req.params; // => Type: string
  const user = req.user; // => Type: { id: string, email: string, role: ... } | undefined

  if (!user) {
    res.status(401).json({ error: "Unauthorized" });
    return;
  }

  res.json({
    userId,
    requestedBy: user.email,
  });
});

// TYPED POST HANDLER
app.post("/users", authMiddleware, (req: TypedRequest<{}, CreateUserBody>, res: Response) => {
  // => Typed body
  const { name, email } = req.body; // => Type: string, string

  if (!name || !email) {
    res.status(400).json({ error: "Name and email required" });
    return;
  }

  res.status(201).json({
    id: "new-user-id",
    name,
    email,
  });
});

// TYPED QUERY PARAMETERS
interface SearchQuery {
  q: string;
  page?: string;
  limit?: string;
}

app.get("/search", (req: TypedRequest<{}, {}, SearchQuery>, res: Response) => {
  // => Typed query
  const { q, page = "1", limit = "10" } = req.query; // => Defaults for optional params

  const pageNum = parseInt(page, 10);
  const limitNum = parseInt(limit, 10);

  res.json({
    query: q,
    page: pageNum,
    limit: limitNum,
  });
});

// ASYNC ROUTE HANDLER
const asyncHandler =
  (fn: RequestHandler): RequestHandler =>
  (req, res, next) => {
    // => Wrapper for async handlers
    Promise.resolve(fn(req, res, next)).catch(next); // => Catch errors and pass to error handler
  };

app.get(
  "/async-data",
  asyncHandler(async (req: Request, res: Response) => {
    // => Async handler
    const data = await fetchData(); // => Async operation
    res.json(data);
  }),
);

async function fetchData(): Promise<{ value: string }> {
  return { value: "data" };
}

// ERROR HANDLING MIDDLEWARE
interface ApiError extends Error {
  statusCode?: number;
}

const errorHandler = (err: ApiError, req: Request, res: Response, next: NextFunction) => {
  // => 4 parameters for error handler
  const statusCode = err.statusCode || 500;
  const message = err.message || "Internal Server Error";

  console.error(err);

  res.status(statusCode).json({
    error: message,
  });
};

app.use(errorHandler); // => Must be last middleware

// ROLE-BASED MIDDLEWARE
function requireRole(...roles: Array<"admin" | "user">): RequestHandler {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) {
      res.status(401).json({ error: "Unauthorized" });
      return;
    }

    if (!roles.includes(req.user.role)) {
      res.status(403).json({ error: "Forbidden" });
      return;
    }

    next();
  };
}

app.delete("/users/:userId", requireRole("admin"), (req: TypedRequest<UserParams>, res: Response) => {
  // => Admin-only route
  const { userId } = req.params;
  res.json({ message: `User ${userId} deleted` });
});

Key Takeaway: Use global augmentation to extend Express Request interface. Type route parameters, body, and query with generics. Create type-safe middleware factories. Use 4-parameter signature for error handlers.

Why It Matters: Web framework typing prevents runtime errors from incorrect request property access. Type-safe middleware enables reusable authentication and validation. Essential for building production-grade APIs with web frameworks.

Example 76: Testing Type Utilities with Conditional Types

Test type transformations at compile time using conditional types and never.

// ASSERT EQUAL TYPE
type AssertEqual<T, U> = (<V>() => V extends T ? 1 : 2) extends <V>() => V extends U ? 1 : 2 ? true : false; // => Checks structural equality

type Test1 = AssertEqual<string, string>; // => Type: true
type Test2 = AssertEqual<string, number>; // => Type: false

// ASSERT EXTENDS
type AssertExtends<T, U> = T extends U ? true : false;

type Test3 = AssertExtends<"hello", string>; // => Type: true
type Test4 = AssertExtends<string, "hello">; // => Type: false

// EXPECT ERROR MARKER
type ExpectError<T> = T; // => Mark locations where errors expected

// COMPILE-TIME TESTS
type Tests = [
  // Test case 1: Partial makes all props optional
  AssertEqual<Partial<{ a: string; b: number }>, { a?: string; b?: number }>,

  // Test case 2: Required makes all props required
  AssertEqual<Required<{ a?: string; b?: number }>, { a: string; b: number }>,

  // Test case 3: Pick selects properties
  AssertEqual<Pick<{ a: string; b: number; c: boolean }, "a" | "b">, { a: string; b: number }>,

  // Test case 4: Omit removes properties
  AssertEqual<Omit<{ a: string; b: number; c: boolean }, "c">, { a: string; b: number }>,

  // Test case 5: ReturnType extracts return type
  AssertEqual<ReturnType<() => string>, string>,
];

// IF ALL TESTS PASS, Tests type is [true, true, true, true, true]
// IF ANY TEST FAILS, Tests type includes false

// TEST CUSTOM UTILITY TYPE
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

type DeepReadonlyTest = AssertEqual<
  DeepReadonly<{
    a: string;
    b: {
      c: number;
    };
  }>,
  {
    readonly a: string;
    readonly b: {
      readonly c: number;
    };
  }
>; // => Test: true

// TEST FUNCTION SIGNATURE
type Add = (a: number, b: number) => number;
type Multiply = (a: number, b: number) => number;

type FunctionTest = AssertEqual<Add, Multiply>; // => Test: true (same signature)

// TEST UNION TYPES
type UnionTest1 = AssertEqual<string | number, number | string>; // => Test: true (order doesn't matter)
type UnionTest2 = AssertEqual<string | number, string | number | boolean>; // => Test: false

// TEST NEVER TYPE
type NeverTest1 = AssertEqual<never, never>; // => Test: true
type NeverTest2 = AssertEqual<never, string>; // => Test: false

// ASSERT ALL TRUE HELPER
type AssertAllTrue<T extends readonly boolean[]> = T[number] extends true ? true : false;

type AllTests = AssertAllTrue<Tests>; // => Type: true if all tests pass

// DESCRIBE-IT PATTERN FOR TESTS
type DescribePickUtility = {
  "should select specified properties": AssertEqual<Pick<{ a: string; b: number }, "a">, { a: string }>;

  "should handle multiple properties": AssertEqual<
    Pick<{ a: string; b: number; c: boolean }, "a" | "c">,
    { a: string; c: boolean }
  >;

  "should handle empty object": AssertEqual<Pick<{}, never>, {}>;
};

type PickTests = AssertAllTrue<DescribePickUtility[keyof DescribePickUtility][]>;

Key Takeaway: Use conditional types to test type transformations at compile time. AssertEqual checks structural type equality. Organize tests in object types with descriptive keys.

Why It Matters: Compile-time type tests prevent regressions in utility types. Type-level testing ensures complex transformations work correctly. Essential for maintaining type utility libraries and framework type systems.

Example 77: TSConfig Advanced Configuration

Configure TypeScript compiler for optimal type checking, performance, and compatibility.

// tsconfig.json - Production configuration
{
  "compilerOptions": {
    // LANGUAGE FEATURES
    "target": "ES2022", // => Output ECMAScript version
    "lib": ["ES2022", "DOM", "DOM.Iterable"], // => Include standard library types
    "module": "ESNext", // => Module system
    "jsx": "react-jsx", // => JSX transformation (React 17+)

    // MODULE RESOLUTION
    "moduleResolution": "bundler", // => Module resolution strategy (Node16, bundler)
    "resolveJsonModule": true, // => Import JSON files
    "allowImportingTsExtensions": false, // => .ts imports (for bundlers only)
    "baseUrl": "./src", // => Base directory for non-relative imports
    "paths": {
      // => Path mapping for module aliases
      "@components/*": ["components/*"],
      "@utils/*": ["utils/*"],
      "@types/*": ["types/*"]
    },

    // EMIT
    "outDir": "./dist", // => Output directory
    "rootDir": "./src", // => Root source directory
    "declaration": true, // => Generate .d.ts files
    "declarationMap": true, // => Source maps for .d.ts
    "sourceMap": true, // => Generate source maps
    "removeComments": true, // => Strip comments from output
    "noEmit": false, // => Emit output (false for tsc, true for bundlers)
    "importHelpers": true, // => Import tslib helpers (smaller output)
    "downlevelIteration": true, // => Emit correct for-of for older targets

    // TYPE CHECKING - STRICT
    "strict": true, // => Enable all strict checks
    "noImplicitAny": true, // => Error on 'any' type (included in strict)
    "strictNullChecks": true, // => Strict null checking (included in strict)
    "strictFunctionTypes": true, // => Strict function type checking
    "strictBindCallApply": true, // => Strict bind/call/apply
    "strictPropertyInitialization": true, // => Class properties must be initialized
    "noImplicitThis": true, // => Error on 'this' with implied 'any'
    "alwaysStrict": true, // => Parse in strict mode

    // TYPE CHECKING - ADDITIONAL
    "noUnusedLocals": true, // => Error on unused local variables
    "noUnusedParameters": true, // => Error on unused function parameters
    "noImplicitReturns": true, // => Error when not all code paths return
    "noFallthroughCasesInSwitch": true, // => Error on switch fallthrough
    "noUncheckedIndexedAccess": true, // => Indexed access returns T | undefined
    "noImplicitOverride": true, // => Require 'override' keyword
    "allowUnusedLabels": false, // => Error on unused labels
    "allowUnreachableCode": false, // => Error on unreachable code

    // INTEROP
    "esModuleInterop": true, // => Emit __importStar/__importDefault helpers
    "allowSyntheticDefaultImports": true, // => Allow default imports from modules without default export
    "forceConsistentCasingInFileNames": true, // => Error on case-sensitive imports
    "isolatedModules": true, // => Ensure each file can be transpiled independently

    // EXPERIMENTAL
    "experimentalDecorators": true, // => Enable decorator syntax
    "emitDecoratorMetadata": true, // => Emit metadata for decorators

    // ADVANCED
    "skipLibCheck": true, // => Skip type checking of .d.ts files (faster)
    "incremental": true, // => Enable incremental compilation
    "tsBuildInfoFile": "./.tsbuildinfo" // => Incremental info file location
  },

  "include": ["src/**/*"], // => Files to include
  "exclude": ["node_modules", "dist", "**/*.spec.ts"] // => Files to exclude
}

Code explanation:

// STRICT NULL CHECKS IMPACT
function getLength(str: string | null): number {
  // With strictNullChecks: true
  // return str.length; // => Error: Object is possibly 'null'

  // Correct approach
  return str?.length ?? 0; // => Optional chaining + nullish coalescing
}

// NO UNCHECKED INDEXED ACCESS
const users: Record<string, { name: string }> = {};

// With noUncheckedIndexedAccess: true
const user = users["key"]; // => Type: { name: string } | undefined (not just { name: string })

if (user) {
  console.log(user.name); // => Safe access after check
}

// ISOLATED MODULES - CONST ENUM LIMITATION
// With isolatedModules: true, const enums must have values
// const enum Color { Red, Green } // => Error: const enums not supported

enum Color {
  Red = 0,
  Green = 1,
} // => Regular enum works

// PATH MAPPING USAGE
import { Button } from "@components/Button"; // => Resolves to ./src/components/Button
import { formatDate } from "@utils/date"; // => Resolves to ./src/utils/date

Key Takeaway: Enable strict for maximum type safety. Use noUncheckedIndexedAccess for safer indexed access. Configure paths for module aliases. Enable skipLibCheck for faster builds.

Why It Matters: TSConfig directly impacts type safety and developer experience. Proper configuration catches more bugs at compile time. Path mapping simplifies import statements. Essential for professional TypeScript projects.

Example 78: Module Resolution Strategies

Understand module resolution to fix import errors and configure bundlers correctly.

// CLASSIC RESOLUTION (deprecated, avoid)
// import { Button } from "./components/Button";
// Looks for: Button.ts, Button.tsx, Button.d.ts

// NODE RESOLUTION (legacy Node.js)
// import { Button } from "./components/Button";
// Looks for:
//   1. ./components/Button.ts
//   2. ./components/Button.tsx
//   3. ./components/Button/index.ts
//   4. ./components/Button/package.json (check "types" field)

// NODE16/NODENEXT RESOLUTION (modern Node.js ESM)
// Requires explicit file extensions for relative imports
import { Button } from "./components/Button.js"; // => .js extension (even for .ts files!)
// => TypeScript strips extension during emit

// For node_modules, respects package.json "exports" field
import { createElement } from "react"; // => Resolves via package.json "exports"

// BUNDLER RESOLUTION (recommended for Webpack/Vite/esbuild)
// Works like Node but allows omitting extensions
import { Button } from "./components/Button"; // => Bundler resolves to Button.ts/tsx
import config from "./config.json"; // => JSON import (with resolveJsonModule: true)

// PACKAGE.JSON EXPORTS FIELD
// package.json
{
  "name": "my-library",
  "version": "1.0.0",
  "type": "module", // => ESM module
  "exports": {
    ".": {
      // => Main entry point
      "types": "./dist/index.d.ts", // => TypeScript types
      "import": "./dist/index.js", // => ESM import
      "require": "./dist/index.cjs" // => CommonJS require
    },
    "./utils": {
      // => Subpath export
      "types": "./dist/utils.d.ts",
      "import": "./dist/utils.js"
    }
  },
  "main": "./dist/index.cjs", // => Fallback for old tools
  "module": "./dist/index.js", // => Fallback for bundlers
  "types": "./dist/index.d.ts" // => Fallback for TypeScript
}

// USAGE OF SUBPATH EXPORTS
import { main } from "my-library"; // => Resolves to ./dist/index.js
import { formatDate } from "my-library/utils"; // => Resolves to ./dist/utils.js

// AMBIENT MODULE DECLARATIONS
// For modules without type definitions
declare module "untyped-library" {
  // => Declare module shape
  export function doSomething(value: string): number;
  export const VERSION: string;
}

import { doSomething, VERSION } from "untyped-library"; // => Now typed
const result = doSomething("test"); // => Type: number

// WILDCARD MODULE DECLARATIONS
declare module "*.css" {
  // => All CSS imports
  const content: { [className: string]: string };
  export default content;
}

import styles from "./App.css"; // => Type: { [className: string]: string }
const className = styles.container; // => Type: string

declare module "*.svg" {
  // => SVG as React component
  const content: React.FC<React.SVGProps<SVGSVGElement>>;
  export default content;
}

import Logo from "./logo.svg"; // => Type: React.FC

Key Takeaway: Use "moduleResolution": "bundler" for most projects. Node16/NodeNext require explicit .js extensions. Configure package.json “exports” for libraries. Use ambient declarations for untyped modules.

Why It Matters: Module resolution affects how TypeScript finds files and type definitions. Incorrect configuration causes “Cannot find module” errors. Proper package.json exports enable conditional exports for ESM/CJS. Essential for library authors and build tool integration.

Example 79: Compiler API Basics

Use TypeScript Compiler API to programmatically analyze and transform code.

import * as ts from "typescript";

// PARSE SOURCE FILE
function parseCode(code: string): ts.SourceFile {
  return ts.createSourceFile(
    "example.ts", // => File name
    code, // => Source code
    ts.ScriptTarget.Latest, // => Language version
    true, // => Set parent nodes
  );
}

const code = `
  const x: number = 42;
  function greet(name: string): string {
    return \`Hello, \${name}!\`;
  }
`;

const sourceFile = parseCode(code);
console.log("File name:", sourceFile.fileName); // => "example.ts"
console.log("Statements:", sourceFile.statements.length); // => 2 (const + function)

// VISIT AST NODES
function visitNode(node: ts.Node, depth: number = 0): void {
  const indent = "  ".repeat(depth);
  console.log(`${indent}${ts.SyntaxKind[node.kind]}`); // => Node kind name

  node.forEachChild((child) => visitNode(child, depth + 1)); // => Recursive traversal
}

visitNode(sourceFile);
// Output:
// SourceFile
//   VariableStatement
//     VariableDeclarationList
//       VariableDeclaration
//         Identifier (x)
//         NumberKeyword
//         NumericLiteral (42)
//   FunctionDeclaration
//     ...

// FIND SPECIFIC NODES
function findFunctions(sourceFile: ts.SourceFile): ts.FunctionDeclaration[] {
  const functions: ts.FunctionDeclaration[] = [];

  function visit(node: ts.Node) {
    if (ts.isFunctionDeclaration(node)) {
      // => Type guard for function
      functions.push(node);
    }
    node.forEachChild(visit);
  }

  visit(sourceFile);
  return functions;
}

const functions = findFunctions(sourceFile);
functions.forEach((fn) => {
  console.log("Function name:", fn.name?.text); // => "greet"
});

// TRANSFORM AST
function addConsoleLog(sourceFile: ts.SourceFile): ts.SourceFile {
  const transformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
    return (rootNode) => {
      function visit(node: ts.Node): ts.Node {
        if (ts.isFunctionDeclaration(node) && node.body) {
          // => Transform function body
          const consoleLog = ts.factory.createExpressionStatement(
            // => Create console.log statement
            ts.factory.createCallExpression(
              ts.factory.createPropertyAccessExpression(
                ts.factory.createIdentifier("console"),
                ts.factory.createIdentifier("log"),
              ),
              undefined,
              [ts.factory.createStringLiteral("Function called")],
            ),
          );

          const newBody = ts.factory.updateBlock(
            node.body,
            [consoleLog, ...node.body.statements], // => Prepend console.log
          );

          return ts.factory.updateFunctionDeclaration(
            node,
            node.modifiers,
            node.asteriskToken,
            node.name,
            node.typeParameters,
            node.parameters,
            node.type,
            newBody,
          );
        }

        return ts.visitEachChild(node, visit, context);
      }

      return ts.visitNode(rootNode, visit) as ts.SourceFile;
    };
  };

  const result = ts.transform(sourceFile, [transformer]);
  return result.transformed[0];
}

// EMIT TRANSFORMED CODE
function emitCode(sourceFile: ts.SourceFile): string {
  const printer = ts.createPrinter();
  return printer.printFile(sourceFile);
}

const transformed = addConsoleLog(sourceFile);
console.log(emitCode(transformed));
// Output:
// const x: number = 42;
// function greet(name: string): string {
//   console.log("Function called");
//   return `Hello, ${name}!`;
// }

// TYPE CHECKING WITH COMPILER API
function checkTypes(fileNames: string[], options: ts.CompilerOptions): void {
  const program = ts.createProgram(fileNames, options); // => Create program
  const emitResult = program.emit(); // => Emit files

  const allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics); // => Get diagnostics

  allDiagnostics.forEach((diagnostic) => {
    if (diagnostic.file) {
      const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!);
      const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
      console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
    } else {
      console.log(ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"));
    }
  });
}

Key Takeaway: Use Compiler API to parse, analyze, and transform TypeScript code. Create type checkers for custom linting. Build code generators and migration tools.

Why It Matters: Compiler API powers linting plugins, formatters, and code generation tools. Essential for building custom linters, formatters, and code transformation tools.

Example 80-85: Final Advanced Patterns

These final examples demonstrate production-ready patterns combining multiple advanced features.

Example 80: Builder Pattern with Type-State

// Type-safe builder ensuring required properties set before build
type BuilderState<T, Set extends keyof T = never> = {
  [K in keyof T]: K extends Set ? never : (value: T[K]) => BuilderState<T, Set | K>;
} & (Set extends keyof T ? { build(): T } : {});

Example 81: Functional Programming Utilities

// Compose, pipe, curry with full type inference
function compose<A, B, C>(f: (b: B) => C, g: (a: A) => B): (a: A) => C {
  return (a) => f(g(a));
}

Example 82: Result/Either Monad for Error Handling

type Result<T, E> = Ok<T> | Err<E>;
class Ok<T> {
  constructor(private value: T) {}
}
class Err<E> {
  constructor(private error: E) {}
}

Example 83: Mapped Type Modifiers Advanced

type Mutable<T> = { -readonly [K in keyof T]-?: T[K] }; // Remove readonly and optional
type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] };

Example 84: Template Literal String Manipulation

type CamelToSnake<S> = S extends `${infer F}${infer R}`
  ? F extends Uppercase<F>
    ? `_${Lowercase<F>}${CamelToSnake<R>}`
    : `${F}${CamelToSnake<R>}`
  : S;

Example 85: Production API Client Pattern

// Type-safe API client with endpoint definitions
class ApiClient<Endpoints> {
  async request<Path, Method>(path: Path, method: Method): Promise<ResponseType<Path, Method>> {
    // Full type safety from endpoint definitions
  }
}

Key Takeaway: Advanced TypeScript combines generics, mapped types, conditional types, template literals, and decorators for production patterns. These patterns eliminate runtime errors through compile-time validation.

Why It Matters: Production patterns demonstrate real-world TypeScript expertise. Type-safe builders prevent configuration errors. Result monads eliminate try-catch boilerplate. API clients ensure correct request/response types. Essential for enterprise-grade applications.

Last updated