TypeScript — Reference

Source: https://www.typescriptlang.org/docs/

TypeScript

  • Created: 2012 by Microsoft (lead architect: Anders Hejlsberg, also of C# and Turbo Pascal)
  • Latest stable: 6.0.3 (2026-04-16); TypeScript 7.0 Beta announced 2026-04-21 — compiler rewritten in Go, ~10x faster
  • Paradigms: multi-paradigm — OO, functional, structural, generic; superset of JavaScript
  • Typing: static, structural (duck-typed at the type level), gradual (opt-in via --strict), with sound-ish but deliberately unsound parts (variance on arrays, function parameter bivariance in some configs)
  • Memory: N/A — TypeScript erases at compile time; runtime is whatever JS engine runs the emitted code
  • Compilation: transpiled to JS by tsc (or swc/esbuild/oxc/Babel with type-stripping). Type-checking is separable from emit. TS 7.0 ships a Go-based native compiler with the same semantics.
  • Primary domains: large-scale frontend apps, full-stack TS (Next.js, NestJS), CLI tools, libraries, infrastructure-as-code (CDK, Pulumi), backends, anywhere JS runs and the team wants a type system
  • Official docs: https://www.typescriptlang.org/docs/

At a glance

  • Owner: Microsoft. Spec: no formal ECMA spec — the implementation is the spec, with intent documented in the Handbook + lib.d.ts ambient types.
  • Compatibility: TS tracks ECMAScript closely; target controls JS output (ES2015..ESNext). lib controls which built-ins the type system knows about.
  • Release cadence: ~3-month minor releases; @beta/@rc tags on npm. TS 6.0 was the last JS-implemented compiler; 7.0 is the Go rewrite (project codename “Corsa”).
  • No runtime. TS adds type syntax + a few runtime helpers (enum, namespace, decorators if you opt in). Strip the types and you have valid JS.

Getting started

Install:

npm i -D typescript          # local (preferred)
npm i -g typescript          # global tsc
# or
pnpm add -D typescript
bun add -d typescript

For TS 7 native compiler (when GA): a single binary, no Node required.

Hello world:

// hello.ts
const greet = (name: string): string => `Hello, ${name}!`;
console.log(greet("world"));

Compile + run: tsc hello.ts && node hello.js. Or run directly: tsx hello.ts, bun hello.ts, deno run hello.ts, or node --experimental-strip-types hello.ts (Node 22.6+).

Project layout:

myapp/
  package.json
  tsconfig.json
  src/
    index.ts
  test/
    index.test.ts
  dist/                # tsc output (or use a bundler)

tsconfig.json minimum-viable:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "dist"
  },
  "include": ["src/**/*"]
}

Build / runner tools:

  • tsc — official, slow but canonical for type-checking. TS 7 native compiler is much faster.
  • tsx, ts-node — run .ts directly in Node.
  • swc, esbuild, oxc — strip types only (no checking), 10-100x faster than tsc emit.
  • node --experimental-strip-types (22.6+) and Bun/Deno run .ts natively.

Playground: https://www.typescriptlang.org/play

Basics

Primitive types: number, bigint, string, boolean, symbol, undefined, null, void, never, unknown, any, object. Literal types: "red", 42, true. Tuples: [string, number].

Variables: same as JS — const, let. Type annotations are optional and inferred from initializers / context.

const port: number = 8080;
let user = { name: "alice" };  // inferred: { name: string }

Control flow: identical to JS. Type narrowing by typeof, instanceof, in, equality, custom predicates (x is Foo), discriminated unions.

Functions:

function fetchAt<T>(url: string, opts: { timeout?: number } = {}): Promise<T> { ... }
const log = (msg: string, level: "info" | "warn" = "info") => console[level](msg);

Overloads via multiple signatures sharing one implementation:

function pad(n: number): string;
function pad(n: number, ch: string): string;
function pad(n: number, ch = "0") { return String(n).padStart(2, ch); }

Strings & collections: all JS — plus literal-typed arrays / tuples and Readonly<T>.

Intermediate

Type system depth:

  • Generics: function id<T>(x: T): T, with constraints <T extends keyof O>, defaults <T = string>.
  • Inference: flow-based, contextual; infer extracts within conditional types.
  • Variance: functions are bivariant on parameters with strictFunctionTypes: false, contravariant with it on. Arrays are covariant (unsound, like Java arrays).
  • Union & intersection: A | B, A & B. Discriminated unions are the workhorse pattern.
  • Utility types: Partial, Required, Readonly, Pick, Omit, Record, Exclude, Extract, NonNullable, Parameters, ReturnType, Awaited, Uppercase/Lowercase/Capitalize/Uncapitalize.

Modules: ESM only in modern code. import { x } from "./mod.js" (note: .js extension even in source, with NodeNext resolution). import type { X } for type-only imports (erased). Path aliases via compilerOptions.paths + bundler/resolver support.

Errors: same as JS (exceptions). try/catch (e: unknown) then narrow with instanceof Error. Result-type pattern is popular but not built-in:

type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };

neverthrow, effect, ts-results are common libs.

Concurrency: all JS — Promises, async/await, AsyncIterators. Promise.all / allSettled / any. Type system tracks Awaited<T> correctly.

File I/O / networking: runtime-dependent. Use @types/node for Node, lib.dom.d.ts for browser, @types/bun / @types/deno for those.

Stdlib highlights (type side): lib.es*.d.ts files in the TS install describe the JS standard library. lib.dom.d.ts describes the browser. @types/* packages from DefinitelyTyped (https://github.com/DefinitelyTyped/DefinitelyTyped) supply third-party JS lib types.

Advanced

Memory model & GC: delegated to runtime (V8 / JSC / SpiderMonkey). TS adds zero overhead — types are erased.

Concurrency: delegated to runtime. TS does help with type-safe Worker messaging — type the MessagePort payload via discriminated unions.

FFI / interop: types for node:*, WebAssembly (WebAssembly.Module), napi-rs, bun:ffi, Deno.dlopen. WASM bindings via wasm-bindgen produce .d.ts.

Reflection: TS has none at runtime by design. typeof x returns JS primitive name. Tools that fake reflection use:

  • Decorators with --emitDecoratorMetadata + reflect-metadata (legacy, used by NestJS/TypeORM).
  • TS-Reflect, tsyringe, class-transformer.
  • Schema-first runtime validators: Zod, Valibot, ArkType, TypeBox, io-ts — define once, derive type via z.infer.
  • Compile-time codegen: ts-morph, ts-patch, custom transformers.

Performance tuning (compiler):

  • tsc --extendedDiagnostics — show parse/check/emit times.
  • tsc --generateTrace traceDir then open in Chrome chrome://tracing.
  • Incremental builds: tsc -b --incremental.
  • Project references for monorepos.
  • skipLibCheck: true for big node_modules.
  • TS 7 (Go compiler) — typically 10x faster type-check; same diagnostics.

God mode

Conditional types + infer:

type ReturnType<T> = T extends (...a: any) => infer R ? R : never;
type ElementOf<T> = T extends readonly (infer U)[] ? U : never;

Mapped types:

type Mutable<T> = { -readonly [K in keyof T]: T[K] };
type Stringify<T> = { [K in keyof T]: string };

Template literal types:

type Route = `/${"users" | "posts"}/${string}`;
type Camel<S> = S extends `${infer A}_${infer B}` ? `${A}${Camel<Capitalize<B>>}` : S;

Variadic tuples:

type Tail<T extends any[]> = T extends [any, ...infer R] ? R : [];
function pipe<T, A>(x: T, f: (x: T) => A): A;
function pipe<T, A, B>(x: T, f: (x: T) => A, g: (x: A) => B): B;

Branded / nominal types:

type UserId = string & { readonly __brand: unique symbol };
const make = (s: string) => s as UserId;

Declaration merging:

interface Window { myAnalytics: { track(e: string): void } }
declare global { namespace NodeJS { interface ProcessEnv { API_KEY: string } } }

satisfies operator (4.9+): validate without widening.

const config = { host: "localhost", port: 8080 } satisfies Record<string, string | number>;
config.port.toFixed(2);  // still typed as number

const type parameters (5.0+):

function freeze<const T>(x: T): T { return x; }
const x = freeze(["a", "b"]);   // type: readonly ["a", "b"]

Custom transformer plugins: ts-patch (since TS rejected first-party plugin support in tsc emit). Manipulate the AST during compile — used by tsyringe, @nestjs/cli SWC plugin, ts-auto-mock.

Compiler API: import * as ts from "typescript". Build linters, codemods, custom checkers. ts-morph is the friendlier wrapper.

Type-level computation tricks: Peano-style integer math, type-level parsers, JSON parsers, SQL parsers — see type-challenges and Anders Hejlsberg-flavored insanity. Watch out for Type instantiation is excessively deep and possibly infinite — TS limits recursion depth (typically ~50-100 for tail-recursive conditional types since 4.5).

Runtime stripping options:

  • --isolatedModules is required when using non-tsc transpilers; flags constructs they can’t handle alone.
  • --verbatimModuleSyntax (5.0+) — emit imports exactly as written, removing the import type / import ambiguity.
  • TC39 type-stripping proposal (Stage 1) would let JS engines parse-and-ignore TS-style annotations.

Idioms & style

  • Naming: camelCase for vars/functions, PascalCase for classes/types/interfaces/enums. No I-prefix on interfaces (Microsoft’s own guidance contradicts old C# habit).
  • Formatters: Prettier, Biome, dprint — all support TS.
  • Linters: @typescript-eslint with eslint, Biome, oxlint. Recommended ruleset: @typescript-eslint/recommended-type-checked.
  • Style guides: Microsoft’s TypeScript Coding Guidelines (internal, public excerpts), Google TypeScript Style Guide (https://google.github.io/styleguide/tsguide.html), tsconfig/bases.
  • Idiomatic patterns:
    • interface for object/class shapes you might extend, type for unions, intersections, mapped/conditional types. Both work for most cases.
    • Discriminated unions over class hierarchies for state machines.
    • unknown not any at I/O boundaries; narrow with type guards or Zod parse.
    • as const to lock literal types in fixtures and config.
    • satisfies for “this matches a shape but keep my narrower type.”
    • Avoid as (type assertions) — almost always means a missing guard or a bad type.
    • Prefer readonly arrays and Readonly<T> for inputs.
  • Reviewer tells: stray any, missing await, // @ts-ignore without explanation (use @ts-expect-error so it errors when fixed), Object (use object or a shape), Function (use a specific signature), enum (prefer literal unions or as const objects).

Ecosystem

DomainTools
FrontendReact, Vue, Svelte, Solid, Angular (TS-first), Lit, Qwik
Meta-frameworksNext.js, Nuxt, SvelteKit, Remix, Astro, Qwik City
BackendNestJS, Fastify (TS-friendly), Hono, ElysiaJS, AdonisJS, tRPC, Encore
Schema / validationZod, Valibot, ArkType, TypeBox, Effect/Schema, io-ts, Yup
ORMPrisma, Drizzle, Kysely, MikroORM, TypeORM
BuildVite, esbuild, swc, tsc, Turbopack, Rolldown, oxc
TestVitest (TS-first), Jest, node:test, Playwright, Mocha
MonorepoNx, Turborepo, Moon, pnpm workspaces, Bun workspaces
StateTanStack Query, Zustand, Jotai, Redux Toolkit, MobX
LLM SDKsOpenAI, Anthropic, Vercel AI SDK, LangChain.js, Mastra
Notable usersMicrosoft (origin, VS Code), Slack, Airbnb, Stripe, Vercel, Bloomberg, Discord, Shopify (Hydrogen)

Gotchas

  • Type erasure: runtime has no clue about your types. instanceof only works on classes; for shapes you need a runtime validator (Zod et al.).
  • any is contagious — once it touches a value, all derived values become any. Prefer unknown.
  • Bivariant function params — without strictFunctionTypes, callbacks can be substituted unsoundly.
  • Excess property checks only fire on object literals, not on variables. fn({ extra: 1 }) errors but fn(obj) may not.
  • enum — emits runtime objects, has reverse-mappings, doesn’t tree-shake well. Prefer as const objects + literal unions.
  • never propagatesPromise<never> is a Promise that never resolves; check your generics.
  • Module resolution surprisesnode vs node10 vs node16 vs bundler vs nodenext — pick deliberately.
  • .js extension in TS source when emitting ESM — required by Node ESM resolver, looks weird at first.
  • paths aliases without a runtime resolver (tsx, ts-node, or bundler) will fail at runtime.
  • useDefineForClassFields flips field initialization semantics between TC39 and old TS behavior; common with Angular/MobX surprises.
  • Newcomers from C#/Java: TS is structural, not nominal. class Foo {} and class Bar {} with the same shape are assignable. Use brands.
  • Newcomers from JS: types disappear at runtime. Validate user input with Zod or similar before trusting types.

Citations