DEV Community

楊東霖
楊東霖

Posted on • Originally published at devplaybook.cc

TypeScript Performance Optimization 2026: Compile Speed, Runtime Efficiency, and Type Safety

TypeScript Performance Optimization 2026: Compile Speed, Runtime Efficiency, and Type Safety

TypeScript adds zero runtime cost — types are erased at compile time. But TypeScript's compiler can become a bottleneck in large codebases, and poor TypeScript patterns can cause bundle size increases and slower JavaScript. Here's how to optimize both.

Part 1: TypeScript Compile Performance

Measure First

# Built-in diagnostics
tsc --diagnostics

# Detailed per-file timing
tsc --extendedDiagnostics

# Profile compilation
tsc --generateTrace trace-output/
# Then analyze with: npx @typescript/analyze-trace trace-output/
Enter fullscreen mode Exit fullscreen mode

Common output from --extendedDiagnostics:

Files:                         1,247
Lines of Library:             42,382
Lines of Definitions:          8,921
Lines of TypeScript:         156,847
Parse Time:                   2.34s
Bind Time:                    0.91s
Check Time:                   8.12s   ← Usually the bottleneck
Emit Time:                    1.23s
Total Time:                  12.60s
Enter fullscreen mode Exit fullscreen mode

Isolate the Type-Check Hotspots

The @typescript/analyze-trace tool shows which files take the most time:

npm install -g @typescript/analyze-trace

tsc --generateTrace trace-output
npx @typescript/analyze-trace trace-output
Enter fullscreen mode Exit fullscreen mode

Common culprits: deeply nested conditional types, large union types, and files with many re-exports.


tsconfig Optimizations

{
  "compilerOptions": {
    // Only emit what you need
    "incremental": true,
    "tsBuildInfoFile": ".tsbuildinfo",

    // Faster module resolution
    "moduleResolution": "bundler",  // Fastest for bundler-based projects

    // Skip type checking on node_modules
    "skipLibCheck": true,

    // Only type-check, don't emit (let your bundler handle emit)
    "noEmit": true,

    // Disable strict checks that rarely catch bugs
    // (only if you're willing to accept the trade-off)
    // "noPropertyAccessFromIndexSignature": false,
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "**/*.spec.ts",  // Exclude test files from production build
    "**/*.test.ts"
  ]
}
Enter fullscreen mode Exit fullscreen mode

Project References for Monorepos

// tsconfig.json (root)
{
  "references": [
    { "path": "./packages/core" },
    { "path": "./packages/api" },
    { "path": "./packages/ui" }
  ]
}

// packages/core/tsconfig.json
{
  "compilerOptions": {
    "composite": true,     // Required for project references
    "declarationMap": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Project references enable incremental compilation across packages. Only rebuild changed packages — not the entire monorepo.

Using SWC or esbuild Instead of tsc

For development builds, skip TypeScript compilation entirely:

// package.json
{
  "scripts": {
    "dev": "tsx watch src/index.ts",         // tsx: zero-config, uses esbuild
    "build": "tsc --noEmit && esbuild src/index.ts --bundle --outfile=dist/index.js",
    "typecheck": "tsc --noEmit"
  }
}
Enter fullscreen mode Exit fullscreen mode
  • tsx (TypeScript Execute): runs TypeScript without tsc, uses esbuild — 10-100x faster than ts-node
  • esbuild: compiles TypeScript files in milliseconds — no type checking, just transformation
  • Type checking runs separately in CI: tsc --noEmit

Part 2: Avoiding TypeScript Runtime Pitfalls

TypeScript compiles to JavaScript — the patterns you use in TypeScript affect the JavaScript output.

Avoid Namespace Patterns

TypeScript namespaces compile to IIFEs that prevent tree shaking:

// ❌ Namespace: compiles to non-tree-shakeable IIFE
namespace MathUtils {
  export function add(a: number, b: number): number {
    return a + b;
  }
}

// ✅ ES module: fully tree-shakeable
export function add(a: number, b: number): number {
  return a + b;
}
Enter fullscreen mode Exit fullscreen mode

Avoid Enums — Use as const Instead

Enums compile to runtime objects, adding to your bundle:

// ❌ Enum: compiles to ~100 bytes of runtime code
enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT',
}

// ✅ const assertion: zero runtime cost, same type safety
const Direction = {
  Up: 'UP',
  Down: 'DOWN',
  Left: 'LEFT',
  Right: 'RIGHT',
} as const;

type Direction = typeof Direction[keyof typeof Direction];
// type Direction = 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'
Enter fullscreen mode Exit fullscreen mode

Use const enum When You Must Use Enums

// const enum: inlined at compile time, zero runtime cost
const enum Status {
  Active = 'ACTIVE',
  Inactive = 'INACTIVE',
}

// Compiled JavaScript: Status.Active → 'ACTIVE' (inlined)
if (user.status === Status.Active) { ... }
// → if (user.status === 'ACTIVE') { ... }
Enter fullscreen mode Exit fullscreen mode

Caveat: const enum doesn't work across module boundaries without isolatedModules: false.


Part 3: Advanced Type Techniques for Better Performance

Branded Types (Zero Runtime Cost)

Branded types prevent mixing semantically different strings without runtime overhead:

// Without branding: both are just strings — easy to mix up
function sendEmail(to: string, from: string): void { ... }
sendEmail(fromEmail, toEmail); // No error, but wrong!

// With branding: compile-time safety, zero runtime cost
type Email = string & { readonly _brand: 'Email' };
type UserId = string & { readonly _brand: 'UserId' };

function createEmail(raw: string): Email {
  if (!raw.includes('@')) throw new Error('Invalid email');
  return raw as Email;
}

function sendEmail(to: Email, from: Email): void { ... }

const userId = '123' as UserId;
sendEmail(userId, toEmail); // ✅ Compile error: UserId is not Email
Enter fullscreen mode Exit fullscreen mode

Discriminated Unions for Exhaustive Checks

type ApiResult<T> =
  | { status: 'success'; data: T }
  | { status: 'error'; error: string }
  | { status: 'loading' };

function handleResult<T>(result: ApiResult<T>): string {
  switch (result.status) {
    case 'success':
      return JSON.stringify(result.data);
    case 'error':
      return `Error: ${result.error}`;
    case 'loading':
      return 'Loading...';
    default:
      // TypeScript ensures this is unreachable
      const _exhaustive: never = result;
      return _exhaustive;
  }
}
Enter fullscreen mode Exit fullscreen mode

Avoid Deeply Nested Generics

Deeply nested generics are the #1 cause of slow TypeScript compilation:

// ❌ Deeply nested generic: compilation nightmare
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]
};

type Config = DeepPartial<{
  database: { host: string; port: number; ssl: { cert: string; key: string } };
  redis: { url: string; ttl: number };
  api: { baseUrl: string; timeout: number; headers: Record<string, string> };
}>;

// ✅ Flatten or use specific types
type PartialDatabase = Partial<{
  host: string;
  port: number;
  ssl: { cert: string; key: string };
}>;
Enter fullscreen mode Exit fullscreen mode

Template Literal Types — Use Sparingly

// These are powerful but slow the compiler
type ApiRoute = `/${string}`;
type EventName = `on${Capitalize<string>}`;

// For large union types, prefer explicit unions:
// ❌ Slow
type HttpMethod = Uppercase<'get' | 'post' | 'put' | 'delete' | 'patch'>;

// ✅ Fast
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
Enter fullscreen mode Exit fullscreen mode

Part 4: TypeScript in 2026 — New Performance Features

TypeScript 5.x Performance Improvements

TypeScript 5.0+ introduced several compilation speedups:

  • --moduleDetection: Reduces unnecessary re-analysis of files
  • --verbatimModuleSyntax: Prevents unnecessary type-only import re-exports
  • Faster --watch mode: Better incremental compilation granularity
{
  "compilerOptions": {
    "moduleDetection": "force",
    "verbatimModuleSyntax": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Go-Based TypeScript Compiler (Preview)

Microsoft announced a Go-based rewrite of the TypeScript compiler targeting 10x compile speed improvements. Preview tools should be available for testing in 2026 — watch the TypeScript blog for updates.


Part 5: Measuring TypeScript's Real Cost

# Measure CI type-check time
time npx tsc --noEmit

# Compare with SWC build (no type checking)
time npx swc src -d dist

# Budget: type checking should be < 30s on CI for good DX
Enter fullscreen mode Exit fullscreen mode

GitHub Actions Caching for tsc

- name: Cache TypeScript incremental compilation
  uses: actions/cache@v4
  with:
    path: .tsbuildinfo
    key: tsc-${{ hashFiles('tsconfig.json', 'src/**/*.ts') }}
    restore-keys: |
      tsc-
Enter fullscreen mode Exit fullscreen mode

With incremental compilation and caching, type-check runs can drop from 30s to 3s on unchanged files.


TypeScript Performance Checklist

Compile Performance

  • [ ] Enable incremental: true with .tsbuildinfo
  • [ ] Use skipLibCheck: true
  • [ ] Set moduleResolution: "bundler" for bundler projects
  • [ ] Use project references in monorepos
  • [ ] Replace ts-node with tsx in development

Runtime Performance

  • [ ] Replace enum with as const objects
  • [ ] Use const enum when enums are necessary
  • [ ] Avoid namespace patterns
  • [ ] Keep generic nesting to maximum 3 levels
  • [ ] Prefer explicit union types over computed template literals

Developer Experience

  • [ ] Run type checking separately from building
  • [ ] Cache .tsbuildinfo in CI
  • [ ] Set up @typescript/analyze-trace for profiling

Related Articles


Summary

TypeScript performance has two dimensions: compile speed and runtime efficiency. For compile speed: enable incremental compilation, use skipLibCheck, and replace ts-node with tsx. For runtime efficiency: ditch enums for as const, avoid namespace patterns, and keep generics shallow.

The upcoming Go-based TypeScript compiler will dramatically change the compile speed story — but the runtime patterns matter today. Start with the TypeScript diagnostics output, find your actual bottlenecks, and optimize the things that actually hurt.


Level Up Your Dev Workflow

Found this useful? Explore DevPlaybook — cheat sheets, tool comparisons, and hands-on guides for modern developers.

🛒 Get the DevToolkit Starter Kit on Gumroad — 40+ browser-based dev tools, source code + deployment guide included.

Top comments (0)