TypeScript Performance Tips

Accelerate TypeScript compilation by profiling diagnostics, optimizing recursive types, isolating modules, and using project references.

TL;DR

  1. Profile compilation bottlenecks using the tsc --diagnostics reporting flag.
  2. Eliminate runtime import overhead by adopting explicit import type syntax.
  3. Accelerate rebuilds across monorepos using composite projectReferences build configurations.

Compiler Diagnostics and Profiling

    Basic diagnostic metrics

    Display time spent in each compilation phase.

    # Display summary compilation statistics
    tsc --diagnostics
    Extended diagnostic report

    Output memory usage and detailed I/O breakdown.

    # Print detailed timing and memory metrics
    tsc --extendedDiagnostics
    Chrome trace generation

    Generate timeline traces for Chrome DevTools.

    # Output trace files into ./trace-dir
    tsc --generateTrace ./trace-dir
    Incremental build caching

    Cache build state to accelerate re-runs.

    # tsconfig compilerOptions:
    # { "incremental": true }
    tsc --build

Type Definition Optimization

    Intermediate type aliases

    Decompose complex generics to enable caching.

    // Bad: deeply nested inline evaluation
    // type Nested = Promise<Record<string, Array<User>>>;
    type UserList = Array<string>;
    type UserRecord = Record<string, UserList>;
    type ResultPromise = Promise<UserRecord>;
    Prefer interfaces for shapes

    Use interface declarations for caching benefits.

    // Interfaces are cached by compiler:
    interface UserProfile {
      id: string;
      name: string;
    }
    Narrow union sizes

    Partition large unions into distinct subtypes.

    type SuccessCode = 200 | 201 | 204;
    type ErrorCode = 400 | 404 | 500;
    type HttpCode = SuccessCode | ErrorCode;
    Const assertions over widening

    Freeze literals without runtime overhead.

    const HTTP_METHODS = [
      "GET",
      "POST",
      "DELETE"
    ] as const;
    type Method = typeof HTTP_METHODS[number];

Type-Only Imports and Syntax

    Dedicated type imports

    Import types that are fully erased during emit.

    import type { Request, Response } from "express";
    import express from "express";
    Inline type import specifier

    Mix value and type imports cleanly.

    import {
      readFile,
      type FileHandle
    } from "node:fs/promises";
    Verbatim module syntax

    Enforce type import elision rules.

    // In tsconfig.json compilerOptions:
    // { "verbatimModuleSyntax": true }
    import type { Config } from "./config";
    Type-only re-exports

    Export types without importing runtime code.

    export type { UserProfile } from "./user";
    export type { AccountSettings } from "./account";

Structural Sharing and Deduplication

    Base interface extension

    Share common properties across model types.

    interface Entity {
      id: string;
      createdAt: number;
    }
    interface Article extends Entity {
      title: string;
    }
    Declaration merging

    Extend global interfaces cleanly.

    interface Window {
      analyticsTracker?: (name: string) => void;
    }
    Utility mapped derivations

    Derive variants from single source of truth.

    interface Todo {
      title: string;
      completed: boolean;
    }
    type TodoUpdate = Partial<Todo>;
    type ReadonlyTodo = Readonly<Todo>;
    Discriminated unions over generics

    Simplify compiler checks with discriminant tags.

    type TaskState =
      | { state: "idle" }
      | { state: "loading" }
      | { state: "done"; data: string };

Build Tool and Project Architecture

    Skip external lib check

    Skip type checking on node_modules d.ts.

    {
      "compilerOptions": {
        "skipLibCheck": true
      }
    }
    Isolated modules enforcement

    Ensure each file transpiles independently.

    {
      "compilerOptions": {
        "isolatedModules": true
      }
    }
    Project references

    Split monorepos into incremental sub-projects.

    {
      "references": [
        { "path": "./packages/core" },
        { "path": "./packages/api" }
      ]
    }
    Composite package flag

    Enable project reference emission contracts.

    {
      "compilerOptions": {
        "composite": true,
        "declaration": true
      }
    }

Tips

  1. Enable skipLibCheck in compiler options to bypass type verification of external declaration files and immediately cut build times in half.
  2. Prefer interface over type aliases for object definitions, as the compiler caches interface declarations much more efficiently during type checking.

Warnings

  1. Avoid deeply nested recursive generic types like DeepPartial, which can cause exponential compiler expansion and catastrophic memory exhaustion.
  2. Omitting isolatedModules when using non-tsc transpilers like esbuild can lead to silent runtime crashes on const enums and re-exports.

In Practice

FAQ