TypeScript is a typed superset of JavaScript that adds a static type system and compile-time checks while still running as plain JavaScript after compilation. It matters because it lets teams model data shapes, APIs, and module boundaries explicitly, catching many bugs before runtime and improving editor tooling (navigation, refactors, autocomplete). The key mental model is that TypeScript's checking is mostly structural and happens at compile time — your types don't exist at runtime unless you write runtime validation. TypeScript 5.x has added significant capabilities including Stage 3 decorators, explicit resource management (using/await using), the NoInfer<T> utility type, inferred type predicates, and --erasableSyntaxOnly for Node.js type-stripping workflows.
15 tables, 160 concepts. Select a concept node to jump to its table row.
Table 1: Type Annotations & Primitive Types
The starting point: how you attach a type to a value, and the built-in primitives you'll annotate with most often. Often you don't write the type at all — inference reads it from the initializer — so the real skill is knowing when an explicit annotation earns its keep. Pay special attention to the three special types at the bottom: any switches checking off, unknown is the safe top type you must narrow first, and never marks code that can't produce a value.
| Concept | Example | Description |
|---|---|---|
let n: number = 42 | Explicitly assigns a static type to a variable, parameter, or return value. | |
const s = "hello" | Compiler infers a type from an initializer or usage without an annotation. | |
let name: string = "Ada" | Primitive text type. | |
let pi: number = 3.14159 | Primitive floating-point numeric type (JS number). | |
let ok: boolean = true | Primitive true/false type. | |
const id: bigint = 123n | Primitive for arbitrary-size integers (JS bigint). | |
const key: unique symbol = Symbol("k") | • Primitive for unique identifiers • unique symbol supports nominal-like keys. | |
let x: string | undefined | • Represent absence • require explicit handling under strictNullChecks. | |
let data: any = JSON.parse(text) | • Disables type checking for the value — escape hatch • avoid in new code. | |
let data: unknown = JSON.parse(text) | • Safer top type than any• must be narrowed before property access or calls. | |
function log(msg: string): void { console.log(msg) } | Indicates a function returns no useful value. | |
function fail(msg: string): never { throw new Error(msg) } | Indicates no possible value — unreachable code, always-throws, or exhausted union. |
Table 2: Object Shapes (Interfaces, Aliases, Properties)
Most real-world typing is describing the shape of objects, and these are the tools for it. Interfaces and type aliases both name a shape — interfaces add declaration merging and feel natural for extension, aliases handle any type expression — and on top of either you layer optional and readonly properties, index signatures for open-ended keys, and call or construct signatures for objects that are also callable. The excess-property check is the quirk worth remembering: object literals get scrutinized for stray keys in ways stored variables don't.
| Pattern | Example | Description |
|---|---|---|
interface User { id: string; name: string } | • Named object shape • supports extension and declaration merging. | |
type User = { id: string; name: string } | Gives a name to any type expression — primitives, unions, tuples, objects. | |
const u: { id: string; name: string } = { id: "1", name: "Ada" } | Inline property names and types without naming the type. | |
type User = { name: string; email?: string } | Marks a property as possibly absent. | |
type User = { readonly id: string; name: string } | Prevents assignment after initialization at the type-checking level. | |
interface Admin extends User { role: "admin" } | Builds larger shapes from smaller ones via extends. | |
type Headers = { [name: string]: string } | Types objects with unknown keys by specifying key/value types. | |
type Fn = { (x: number): string } | Describes a callable object that may also carry properties. | |
type Ctor = { new (s: string): Date } | Describes a new-able constructor type. | |
const u: User = { id: "1", name: "Ada", extra: 1 } | Object literals checked for unknown properties when directly assigned to a type. |
Table 3: Type Composition (Unions, Intersections, Literals, Enums)
This is where types start combining into something more expressive than a single shape. Unions say "one of these," intersections say "all of these at once," and literal types pin a value down to an exact string or number — together they're the foundation of modeling states precisely. Tuples, as const, and the two flavors of enum round it out, with the notes flagging which constructs emit runtime code (string and numeric enums) versus which vanish at compile time.
| Type | Example | Description |
|---|---|---|
type ID = string | number | Value can be one of multiple types. | |
type WithId = { id: string } & { createdAt: Date } | Value must satisfy all combined types simultaneously. | |
const roles = ["admin", "user"] as const | Infers the narrowest literal and readonly types for an expression. | |
type Role = "admin" | "user" | Restricts values to specific string constants. | |
type Dice = 1 | 2 | 3 | 4 | 5 | 6 | Restricts values to specific numeric constants. | |
const pair: [string, number] = ["a", 1] | Fixed-length array with positional element types. | |
user!.id | Asserts a value is not null/undefined for type checking (erased at runtime). | |
enum Role { Admin = "admin", User = "user" } | • Members have explicit string values at runtime • more debuggable than numeric enums. | |
enum Status { Pending, Done } | • Named constants that emit a runtime JS object • auto-increments from 0. | |
const enum Dir { Up, Down } | • Inlines enum values at emit time — no runtime object • avoid in public declaration files. |
Table 4: Functions (Signatures, Generics, Overloads)
Typing functions covers everything from a plain parameter list to generics that carry types through a call. Generics and their extends constraints are the heart of reusable, type-safe utilities, while overloads let one implementation present several distinct call signatures. The smaller entries — optional, default, and rest parameters, plus newer touches like const type parameters — handle the everyday details of getting arguments and return values typed cleanly.
| Pattern | Example | Description |
|---|---|---|
type Mapper = (x: number) => string | Describes a function's parameter and return types as a type alias. | |
function first<T>(xs: T[]): T { return xs[0] } | Parameterizes types with <T> for reusable, type-safe logic. | |
function len<T extends { length: number }>(x: T) { return x.length } | Restricts a type parameter with extends so required members can be accessed. | |
function parse(x: string): number;<br> function parse(x: number): string;<br> function parse(x: string | number) { return typeof x === "string" ? +x : String(x) } | Multiple call signatures with one implementation below them. | |
function greet(name?: string) {} | • Parameter may be omitted • typed as possibly undefined inside the function. | |
function inc(n: number = 1) { return n + 1 } | Supplies a default value when the argument is missing or undefined. | |
function sum(...ns: number[]) { return ns.reduce((a,b)=>a+b, 0) } | Collects remaining arguments into a typed array. | |
function identity<const T>(val: T): T { return val }identity(["a","b"]) // T inferred as ["a","b"] not string[] | Infers the narrowest literal type from a call-site argument (TS 5.0+). | |
type ApiResult<T = unknown> = { data: T } | Provides a fallback when type arguments are not supplied or inferred. | |
function f(this: Date) { return this.getTime() } | Fake first parameter to type this (erased in output). | |
type Fn = { (x: string): string; tag: string } | Models callable values that also carry properties. |
Table 5: Classes (Members, Modifiers, Inheritance)
TypeScript classes are JavaScript classes with a type layer bolted on: the same fields, methods, and inheritance, plus visibility modifiers, readonly, and implements/abstract for enforcing contracts. Parameter properties are the time-saver — declaring and assigning a field straight from the constructor signature — and the override keyword (with noImplicitOverride) makes inheritance refactors safe. Note that private here is a compile-time guarantee, whereas JS # fields are genuinely private at runtime.
| Concept | Example | Description |
|---|---|---|
class User { constructor(public id: string) {} } | • Defines fields/methods with type annotations • emits a JS class. | |
class User { constructor(public readonly id: string) {} } | Shorthand that declares and initializes a member from a constructor parameter. | |
class A { private secret = 1; protected n = 2; public id = "x" } | • Controls member visibility in type checking • use # for hard JS private fields. | |
class A { readonly id = "x" } | Prevents reassignment after construction at the type-checking level. | |
class Repo implements Iterable<string> { [Symbol.iterator]() { return [][Symbol.iterator]() } } | Checks a class satisfies an interface at compile time. | |
class B extends A {} | Subclassing with super calls and overriding. | |
abstract class Base { abstract run(): void } | • Cannot be instantiated directly • may declare abstract members subclasses must implement. | |
class B extends A { override toString() { return "B" } } | • Replaces a base member • override keyword enforces the base member exists. | |
class A { get x() { return this._x } set x(v: number) { this._x = v } } | Declares typed property accessors. | |
class C { static count = 0; static { C.count = 1 } } | Runs complex static setup code with access to private members (TS 4.4+). | |
// tsconfig.json<br> { "compilerOptions": { "noImplicitOverride": true } } | Requires override keyword on all overriding members for safer refactors. |
Table 6: Narrowing (Type Guards & Control Flow)
Narrowing is how a broad union type collapses to a specific one inside a branch — the compiler watches your control flow and tracks what a value can still be. The everyday guards reuse plain JavaScript checks (typeof, instanceof, in, truthiness, equality), and discriminated unions make this effortless by tagging each variant with a shared literal field. When the built-in checks aren't enough, type predicates and assertion functions let you teach the compiler your own narrowing logic, and the exhaustiveness pattern catches the case you forgot.
| Technique | Example | Description |
|---|---|---|
if (typeof x === "string") x.toUpperCase() | Narrows unions using JS typeof checks. | |
if (e instanceof Error) console.error(e.message) | Narrows via prototype chain checks. | |
if ("id" in obj) obj.id | Narrows based on property existence. | |
if (s) s.trim() | Narrows based on falsy/truthy JavaScript semantics. | |
if (x === null) return | Narrows unions with === / !== comparisons. | |
type Shape = { kind: "circle"; r: number } | { kind: "square"; s: number } | Uses a shared literal field ( kind) to narrow via switch/if checks. | |
function isError(x: unknown): x is Error { return x instanceof Error } | User-defined guard that narrows a value to a specific type. | |
const isString = (x: unknown) => typeof x === "string" | TS 5.5+ automatically infers x is string from a boolean-returning function body — no explicit annotation needed. | |
function assertNonNull(x: unknown): asserts x { if (x == null) throw new Error() } | Narrows by asserting a condition holds when the function returns normally. | |
const _exhaustive: never = value | Forces the compiler to prove all union cases are handled — errors if a case is missed. |
Table 7: Type Operators & Advanced Types
This is type-level programming: operators that compute new types from existing ones rather than just declaring them. keyof, typeof, and indexed access read structure out of other types; mapped types, conditional types, and infer transform and branch on it; template literal types build string types programmatically. It's the most powerful corner of TypeScript and the steepest — but it's what lets a library's types stay perfectly in sync with its data. The satisfies operator is a gentler standout, validating a value against a type without throwing away its narrower inferred shape.
| Operator | Example | Description |
|---|---|---|
const input = document.getElementById("id") as HTMLInputElement | • Overrides TypeScript's inferred type • no runtime effect. Use when you know more than the compiler. | |
const x = value as unknown as TargetType | • Forces conversion between unrelated types via the unknown intermediate• use sparingly. | |
const cfg = { mode: "dev" as const }; type Cfg = typeof cfg | Extracts a type from a runtime value's shape. | |
type Keys = keyof User | Produces a union of property keys from an object type. | |
type UserId = User["id"] | Looks up a property type from another type via T[K]. | |
const routes = { home: "/" } satisfies Record<string, string> | Checks an expression matches a type without widening the expression's own inferred type. | |
type RO<T> = { readonly [K in keyof T]: T[K] } | Creates a new object type by iterating over keys. | |
type IsString<T> = T extends string ? true : false | Chooses a type based on an extends relationship test. | |
type EventName = `on${Capitalize<string>}` | Builds string types via template literal syntax over string unions. | |
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] } | Renames or filters keys while mapping with the as clause. | |
type ToArray<T> = T extends any ? T[] : never | Distributes over unions when the checked type is a naked type parameter. | |
type Elem<T> = T extends (infer U)[] ? U : T | Introduces an inferred type variable inside a conditional type. | |
type ReadRef<out T> = { readonly value: T }type Writer<in T> = { write(x: T): void } | • out = covariant (used in output position only)• in = contravariant (input only); improves checker accuracy (TS 4.7+). | |
type JSON = string | number | boolean | null | JSON[] | { [k: string]: JSON } | • A type alias that references itself • supported since TS 3.7 via deferred resolution. |
Table 8: Utility Types (Built-in)
These ship with TypeScript and save you from hand-writing the same transformations over and over. Partial, Required, Readonly, Pick, and Omit reshape object types; Record builds dictionaries; Exclude, Extract, and NonNullable filter unions; and ReturnType, Parameters, and Awaited extract pieces out of functions and promises. Knowing this set well means you rarely need to reach for mapped or conditional types directly — they're already wrapped up here.
| Type | Example | Description |
|---|---|---|
type Patch = Partial<User> | Makes all properties of T optional. | |
type StrictUser = Required<User> | Makes all properties of T required. | |
type Frozen = Readonly<User> | Makes all properties of T readonly. | |
type UserRef = Pick<User, "id" | "name"> | Selects a subset of properties by key. | |
type PublicUser = Omit<User, "email"> | Removes properties by key. | |
type ById = Record<string, User> | Maps keys K to value type T. | |
type NonString = Exclude<string | number, string> | Removes from T the members assignable to U. | |
type OnlyString = Extract<string | number, string> | Keeps from T only the members assignable to U. | |
type NN = NonNullable<string | null | undefined> | Removes null and undefined from a type. | |
type R = ReturnType<() => Promise<number>> | Extracts a function type's return type. | |
type P = Parameters<(x: string, y: number) => void> | Extracts a function type's parameter tuple. | |
type V = Awaited<Promise<string>> | Recursively unwraps the resolved value type of a promise-like. | |
class C { constructor(a: string, b: number) {} }type P = ConstructorParameters<typeof C> // [string, number] | Extracts a constructor's parameter types as a tuple. | |
type I = InstanceType<typeof User> | Extracts the instance type of a constructor function type. | |
function createStore<T>(init: T, fallback: NoInfer<T>): T { return init ?? fallback } | Blocks type inference from the wrapped position, forcing T to be inferred from other arguments (TS 5.4+). | |
const mixin: ThisType<{ x: number }> = { getX() { return this.x } } | Specifies the this type for methods in an object literal (requires noImplicitThis). | |
type U = Uppercase<"hello"> // "HELLO" | Intrinsic string types that transform string literal types to upper/lower case (TS 4.1+). | |
type C = Capitalize<"world"> // "World" | Intrinsic string types that capitalize or uncapitalize the first character of a string literal type (TS 4.1+). | |
type T = ThisParameterType<(this: Date) => void> // Date | Extracts the this parameter type from a function type. | |
type F = OmitThisParameter<(this: Date, x: string) => void> | Removes the this parameter from a function type. |
Table 9: Decorators
Decorators wrap classes and their members to add or alter behavior — the @-prefixed syntax you've seen in frameworks like Angular and NestJS. TypeScript 5.0 adopted the standardized Stage 3 form, where each decorator receives a context object and can replace what it decorates, and 5.2 added a metadata channel via Symbol.metadata. The crucial caveat is that the new Stage 3 decorators are not compatible with the older experimentalDecorators syntax, so libraries pin themselves to one or the other.
| Decorator | Example | Description |
|---|---|---|
function sealed(Base: typeof C, ctx: ClassDecoratorContext) {} class C {} | • Wraps or replaces a class • receives the class and a context object (TS 5.0 Stage 3 syntax). | |
function log(fn: Function, ctx: ClassMethodDecoratorContext) { return function(this: any, ...args: any[]) { return fn.apply(this, args) } }class C { greet() {} } | • Wraps a method • the replacement function is returned. | |
function init<T>(val: T) { return (_: undefined, _ctx: ClassFieldDecoratorContext) => () => val }class C { ("Ada") name!: string } | • Intercepts field initialization • return an initializer function to override the default value. | |
function bound(fn: Function, ctx: ClassAccessorDecoratorContext) {}class C { accessor name = "Ada" } | Decorates an accessor keyword field, which auto-generates a getter/setter pair. | |
function configurable(fn: Function, ctx: ClassGetterDecoratorContext) {}class C { get x() { return 1 } } | Decorates a getter or setter individually. | |
function meta(_: any, ctx: DecoratorContext) { ctx.metadata.info = "x" } class C {}; C[Symbol.metadata] | • TS 5.2+ adds Symbol.metadata to classes• decorators can store metadata accessible at runtime. | |
// tsconfig.json<br> { "compilerOptions": { "experimentalDecorators": true } } | • Enables the pre-Stage-3 legacy decorator syntax used by Angular, NestJS, and older libraries • incompatible with new Stage 3 decorators. |
Table 10: Modules & Imports
How code is split across files and how TypeScript resolves and emits those boundaries. Standard ESM import/export is fully type-checked, and the type-only variants (import type / export type) signal that a symbol exists purely for the type system and should be erased from the output. The cluster of tsconfig options here — moduleResolution, module, esModuleInterop, paths — is what reconciles TypeScript with the realities of Node.js and bundlers, and is a frequent source of confusing import errors.
| Pattern | Example | Description |
|---|---|---|
import { readFile } from "node:fs/promises" | Standard ECMAScript module syntax, fully type-checked by TS. | |
import type { User } from "./types" | Imports a symbol only for the type system — erased entirely at runtime. | |
export type { User } from "./types" | Re-exports types without a runtime export. | |
import data from "./data.json" with { type: "json" } | • Instructs the runtime how to load a module (e.g., JSON) • ECMAScript 2025, supported from TS 5.3. | |
// tsconfig.json<br> { "compilerOptions": { "verbatimModuleSyntax": true } } | Preserves import/export statements literally and enforces import type for type-only imports. | |
import pkg from "cjs-only" | Covers how TS models default/namespace imports across ESM and CommonJS boundaries. | |
// tsconfig.json<br> { "compilerOptions": { "esModuleInterop": true } } | Adjusts emit and checking for default/namespace imports to match Node.js/bundler behavior. | |
// tsconfig.json<br> { "compilerOptions": { "allowSyntheticDefaultImports": true } } | Allows default imports from modules without a default export (type-checking convenience only). | |
// tsconfig.json<br> { "compilerOptions": { "moduleResolution": "bundler" } } | Selects how imports are resolved: node16, nodenext, or bundler for modern tooling. | |
// tsconfig.json<br> { "compilerOptions": { "module": "nodenext" } } | Selects the emitted module format and influences resolution behavior. | |
// tsconfig.json<br> { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"] } } } | Adds path mapping for module specifiers during type checking and emit. |
Table 11: JSX / TSX
If you write React or any JSX-based UI, these are the knobs that make it type-check. The .tsx extension turns on JSX parsing, the jsx compiler option decides whether that syntax is transformed or preserved, and jsxImportSource points the automatic runtime at the right factory — so you can target Preact or another library instead of React. JSX.IntrinsicElements is where the set of valid built-in tags and their props lives.
| Option | Example | Description |
|---|---|---|
// Component.tsx<br> export function Button() { return <button /> } | Enables JSX syntax in TypeScript source files with .tsx. | |
// tsconfig.json<br> { "compilerOptions": { "jsx": "react-jsx" } } | Controls how JSX is transformed or preserved by the compiler. | |
// tsconfig.json<br> { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "preact" } } | Sets the module for importing jsx/jsxs factories for the automatic runtime. | |
declare namespace JSX { interface IntrinsicElements { div: any } } | Declares valid JSX tags and their prop types for type checking. |
Table 12: TSConfig Core (Type Checking, Emit, Projects)
The compiler options you'll actually set in tsconfig.json, grouped around three jobs: how strictly to check, what to emit, and how to structure multi-package builds. Turning on strict is the single most consequential choice — it bundles a family of safety flags and is the recommended baseline for any new project. The rest control output (target, lib, declaration, noEmit), build speed (incremental, composite, references), and a handful of finer correctness toggles.
| Option | Example | Description |
|---|---|---|
// tsconfig.json<br> { "compilerOptions": { "strict": true } } | Enables a bundle of strict type-checking flags — the recommended baseline for new projects. | |
// tsconfig.json<br> { "compilerOptions": { "strictNullChecks": true } } | Treats null/undefined as distinct types requiring explicit handling. | |
// tsconfig.json<br> { "compilerOptions": { "noImplicitAny": true } } | Errors when the compiler would infer any due to missing type information. | |
// tsconfig.json<br> { "compilerOptions": { "target": "ES2022" } } | Sets the emitted JS language level and enables corresponding downlevel transforms. | |
// tsconfig.json<br> { "compilerOptions": { "lib": ["ES2022", "DOM"] } } | Selects built-in declaration libraries available (e.g., DOM, ES2022). | |
// tsconfig.json<br> { "compilerOptions": { "skipLibCheck": true } } | • Skips type checking of all .d.ts files• improves build speed and avoids third-party type conflicts. | |
// tsconfig.json<br> { "compilerOptions": { "resolveJsonModule": true } } | Allows importing .json files with full type inference from their content. | |
// tsconfig.json<br> { "compilerOptions": { "isolatedModules": true } } | Errors on patterns that require cross-file type information, making single-file transpile tools safe (e.g., esbuild, SWC). | |
// tsconfig.json<br> { "compilerOptions": { "declaration": true } } | Emits .d.ts declaration files describing the public types of each module. | |
// tsconfig.json<br> { "compilerOptions": { "noEmit": true } } | Runs type checking only without writing any output files. | |
// tsconfig.json<br> { "compilerOptions": { "incremental": true } } | Saves build info to a .tsbuildinfo file to speed up subsequent compilations. | |
// tsconfig.json<br> { "compilerOptions": { "composite": true } } | Makes a project reference-able in a project references graph and enables build outputs. | |
// tsconfig.json<br> { "references": [{ "path": "../core" }] } | Declares dependencies between TS projects for tsc -b incremental builds. | |
// tsconfig.json<br> { "compilerOptions": { "exactOptionalPropertyTypes": true } } | Makes optional properties more exact: setting undefined explicitly is distinct from the property being absent. | |
// tsconfig.json<br> { "compilerOptions": { "useUnknownInCatchVariables": true } } | Types catch (e) as unknown instead of any for safer error handling. | |
// tsconfig.json<br> { "compilerOptions": { "isolatedDeclarations": true } } | Requires sufficient type annotations on exports so other tools can generate .d.ts files without full type analysis (TS 5.5+). | |
// tsconfig.json<br> { "compilerOptions": { "erasableSyntaxOnly": true } } | Prohibits TS-specific runtime syntax (enums, namespaces, parameter properties) that cannot be stripped by Node.js type-stripping (TS 5.8+). |
Table 13: tsc CLI Workflows
The handful of tsc invocations that cover almost everything you'll do from the command line. tsc --noEmit is the one CI pipelines lean on — type-check without producing any JavaScript — while --watch keeps recompiling as you edit and tsc --init scaffolds a fresh config. The -b build mode is the one to know once a codebase grows into multiple referenced projects, since it rebuilds them in dependency order.
| Command | Example | Description |
|---|---|---|
tsc | Compiles using the nearest tsconfig.json in the directory tree. | |
tsc --noEmit | Runs the type checker without producing any JavaScript output. | |
tsc --watch | Recompiles on file changes continuously. | |
tsc --init | Creates a tsconfig.json scaffold with annotated options. | |
tsc -p tsconfig.json | Compiles using an explicit configuration file path. | |
tsc -b | Builds a referenced project graph in dependency order. | |
tsc -b --watch | Incrementally rebuilds a project reference graph on changes. | |
tsc --version | Prints the installed TypeScript version to stdout. |
Table 14: Declaration Files & Augmentation
Declaration files (.d.ts) carry types without any runtime code — they're how plain JavaScript packages describe their shapes and how you fill gaps in third-party types. Augmentation is the powerful part: declare global and module augmentation let you add members to existing types (a userId on Express's Request, say) without forking the original package, and declaration merging is the underlying mechanism that makes it work.
| Pattern | Example | Description |
|---|---|---|
// index.d.ts<br> export interface User { id: string } | Provides type information for JS at compile time with no runtime code. | |
// globals.d.ts<br> declare const VERSION: string | Declares globals accessible without imports (ambient global library style). | |
// index.d.ts<br> declare module "pkg" { export function f(): void } | Declares the shape of an importable module for untyped JS packages. | |
declare global { interface Window { appVersion: string } } | Augments global types from within a module (requires at least one import or export). | |
declare module "express" { interface Request { userId?: string } } | Adds members to an existing module's declarations without forking the types package. | |
interface Box { size: number }interface Box { color: string } | Multiple declarations with the same name are merged into one combined type. | |
/// <reference types="node" /> | • Declares a type package dependency for the file • use import type in modules instead. | |
/// <reference lib="dom" /> | Explicitly includes a built-in lib (e.g., dom) for the file without changing tsconfig.json. |
Table 15: Explicit Resource Management
A newer TypeScript 5.2 feature that brings deterministic cleanup to JavaScript — think of it as try/finally you don't have to write by hand. Declare a resource with using (or await using for async) and its [Symbol.dispose] method runs automatically when the scope exits, so connections and file handles close even on an early return or thrown error. The Disposable interface defines what makes a class eligible, and DisposableStack groups several resources to tear down together in reverse order.
| Feature | Example | Description |
|---|---|---|
using conn = getConnection() | Automatically calls conn[Symbol.dispose]() when the enclosing scope exits (TS 5.2+, ECMAScript Stage 4). | |
await using conn = await openConnection() | Automatically calls conn[Symbol.asyncDispose]() when the enclosing async scope exits. | |
class Conn { [Symbol.dispose]() { this.close() } } | • The synchronous cleanup method called by using• implement to make a class disposable. | |
class Conn { async [Symbol.asyncDispose]() { await this.close() } } | The asynchronous cleanup method called by await using. | |
function use(r: Disposable) { using _ = r; } | • Interface requiring [Symbol.dispose]()• available in lib: "ES2026" or "esnext.disposable". | |
async function use(r: AsyncDisposable) { await using _ = r; } | Interface requiring [Symbol.asyncDispose](). | |
using stack = new DisposableStack()stack.use(resource); stack.defer(() => cleanup()) | • Aggregates multiple resources for disposal in LIFO order • AsyncDisposableStack for async variants. |