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(orswc/esbuild/oxc/Babelwith 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.tsambient types. - Compatibility: TS tracks ECMAScript closely;
targetcontrols JS output (ES2015..ESNext).libcontrols which built-ins the type system knows about. - Release cadence: ~3-month minor releases;
@beta/@rctags 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,decoratorsif 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 typescriptFor 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.tsdirectly 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.tsnatively.
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;
inferextracts 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 traceDirthen open in Chromechrome://tracing.- Incremental builds:
tsc -b --incremental. - Project references for monorepos.
skipLibCheck: truefor bignode_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 numberconst 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:
--isolatedModulesis required when using non-tsc transpilers; flags constructs they can’t handle alone.--verbatimModuleSyntax(5.0+) — emit imports exactly as written, removing theimport type/importambiguity.- TC39 type-stripping proposal (Stage 1) would let JS engines parse-and-ignore TS-style annotations.
Idioms & style
- Naming:
camelCasefor vars/functions,PascalCasefor classes/types/interfaces/enums. NoI-prefix on interfaces (Microsoft’s own guidance contradicts old C# habit). - Formatters: Prettier, Biome, dprint — all support TS.
- Linters:
@typescript-eslintwitheslint, 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:
interfacefor object/class shapes you might extend,typefor unions, intersections, mapped/conditional types. Both work for most cases.- Discriminated unions over class hierarchies for state machines.
unknownnotanyat I/O boundaries; narrow with type guards or Zod parse.as constto lock literal types in fixtures and config.satisfiesfor “this matches a shape but keep my narrower type.”- Avoid
as(type assertions) — almost always means a missing guard or a bad type. - Prefer
readonlyarrays andReadonly<T>for inputs.
- Reviewer tells: stray
any, missingawait,// @ts-ignorewithout explanation (use@ts-expect-errorso it errors when fixed),Object(useobjector a shape),Function(use a specific signature),enum(prefer literal unions oras constobjects).
Ecosystem
| Domain | Tools |
|---|---|
| Frontend | React, Vue, Svelte, Solid, Angular (TS-first), Lit, Qwik |
| Meta-frameworks | Next.js, Nuxt, SvelteKit, Remix, Astro, Qwik City |
| Backend | NestJS, Fastify (TS-friendly), Hono, ElysiaJS, AdonisJS, tRPC, Encore |
| Schema / validation | Zod, Valibot, ArkType, TypeBox, Effect/Schema, io-ts, Yup |
| ORM | Prisma, Drizzle, Kysely, MikroORM, TypeORM |
| Build | Vite, esbuild, swc, tsc, Turbopack, Rolldown, oxc |
| Test | Vitest (TS-first), Jest, node:test, Playwright, Mocha |
| Monorepo | Nx, Turborepo, Moon, pnpm workspaces, Bun workspaces |
| State | TanStack Query, Zustand, Jotai, Redux Toolkit, MobX |
| LLM SDKs | OpenAI, Anthropic, Vercel AI SDK, LangChain.js, Mastra |
| Notable users | Microsoft (origin, VS Code), Slack, Airbnb, Stripe, Vercel, Bloomberg, Discord, Shopify (Hydrogen) |
Gotchas
- Type erasure: runtime has no clue about your types.
instanceofonly works on classes; for shapes you need a runtime validator (Zod et al.). anyis contagious — once it touches a value, all derived values becomeany. Preferunknown.- 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 butfn(obj)may not. enum— emits runtime objects, has reverse-mappings, doesn’t tree-shake well. Preferas constobjects + literal unions.neverpropagates —Promise<never>is a Promise that never resolves; check your generics.- Module resolution surprises —
nodevsnode10vsnode16vsbundlervsnodenext— pick deliberately. .jsextension in TS source when emitting ESM — required by Node ESM resolver, looks weird at first.pathsaliases without a runtime resolver (tsx, ts-node, or bundler) will fail at runtime.useDefineForClassFieldsflips 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 {}andclass 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
- Official handbook: https://www.typescriptlang.org/docs/handbook/intro.html
- Release notes index: https://www.typescriptlang.org/docs/handbook/release-notes/overview.html
- TS 7 Beta announcement: https://devblogs.microsoft.com/typescript/announcing-typescript-7-beta/
- TS 6.0 release notes: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-6-0.html
- Compiler API: https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API
- DefinitelyTyped: https://github.com/DefinitelyTyped/DefinitelyTyped
- Google TS style guide: https://google.github.io/styleguide/tsguide.html
- Type Challenges: https://github.com/type-challenges/type-challenges
- TC39 type annotations proposal: https://github.com/tc39/proposal-type-annotations
- Wikipedia (version history reference): https://en.wikipedia.org/wiki/TypeScript