Skip to main content

TypeScript Cheatsheet — 100+ Snippets for Types, Generics, Utility Types, and Narrowing

TypeScript cheat sheet — 100+ snippets for types, generics, utility types, narrowing, async patterns.

  • Runs locally
  • Category Developer & DevOps
  • Best for Formatting, validating, shrinking, or inspecting code-adjacent text.
169 snippets
Basics (20)

string — text values

let name: string = "Lei";
let greet: string = `Hello, ${name}`;   // 模板字符串
let multi: string = `line 1
line 2`;                                 // 多行

The `string` primitive accepts single quotes, double quotes, and backtick template literals. Use template literals for interpolation and multi-line strings.

number — int + float in one type

let count: number = 42;
let pi: number = 3.14;
let hex: number = 0xff;          // 255
let bin: number = 0b1010;        // 10
let big: bigint = 9007199254740993n;   // 超过 Number.MAX_SAFE_INTEGER 用 bigint

TypeScript has one `number` for ints and floats (IEEE 754). Beyond Number.MAX_SAFE_INTEGER (2^53 − 1), use `bigint` with the `n` suffix.

boolean — true / false

let done: boolean = false;
let ok: boolean = !!getValue();   // 双感叹号转 boolean

Just `true` and `false`. Convert anything to boolean with the double-bang `!!value` idiom.

null vs undefined — two empties

let a: null = null;
let b: undefined = undefined;
let c: string | null = getValue();   // 可能为 null
let d: string | undefined;           // 默认 undefined

// strictNullChecks 开了之后必须显式处理
if (c !== null) {
  c.toUpperCase();   // ✅ 安全
}

null is an intentional empty, undefined is "not assigned yet". With strictNullChecks on (you should), neither is assignable to other types without an explicit union.

any — the escape hatch

let x: any = "hello";
x = 42;        // 允许
x = { foo: 1 };// 允许
x.bar.baz();   // 不报错,运行时炸

// ❌ 几乎从不该用,优先 unknown

`any` disables type checking entirely. Almost never the right choice — use `unknown` instead. Acceptable only at JS migration boundaries.

unknown — safe any

let x: unknown = JSON.parse(input);

// ❌ 直接用会报错
// x.toUpperCase();

// ✅ 必须先收窄
if (typeof x === "string") {
  x.toUpperCase();   // 这里 x 已经是 string
}

unknown accepts anything but lets you do NOTHING with it until you narrow. The right type for parsed JSON, deserialized data, third-party callbacks.

never — the impossible type

function fail(msg: string): never {
  throw new Error(msg);   // 永远不返回
}

function loop(): never {
  while (true) {}         // 死循环也是 never
}

// 用在穷举检查
const _exhaustive: never = value;

never represents values that never occur — thrown functions, infinite loops, the empty set in a union. Use `let _: never = value` for exhaustiveness checks on discriminated unions.

void — no useful return

function log(msg: string): void {
  console.log(msg);
  // 隐式 return undefined
}

// 注意:void 返回类型的函数 PARAM 可以返回任意值
const cb: () => void = () => 42;   // ✅ 合法,值会被丢弃

void means "no return value the caller should use". Quirk: a callback typed `() => void` accepts a function returning anything — the return is ignored.

Literal types — exact value as type

let dir: "up" | "down" | "left" | "right";
dir = "up";       // ✅
// dir = "north"; // ❌ 不在联合里

let status: 200 | 404 | 500;
let flag: true;   // 唯一一个 true

String, number, and boolean literals can be types — a value must equal one of the listed literals. The foundation of discriminated unions and lookup tables.

Array<T> and T[] — two syntaxes

let xs: number[] = [1, 2, 3];
let ys: Array<number> = [1, 2, 3];   // 同义

// 嵌套时 T[] 更清爽
let grid: number[][] = [[1, 2], [3, 4]];
let grid2: Array<Array<number>> = [[1, 2], [3, 4]];

`T[]` and `Array<T>` are interchangeable. Use `T[]` for short, `Array<T>` when T is complex (Array<string | number>).

readonly arrays — immutable

let xs: readonly number[] = [1, 2, 3];
// xs.push(4);   ❌ Property 'push' does not exist
// xs[0] = 9;    ❌ Index signature is readonly

// 等价语法
let ys: ReadonlyArray<number> = [1, 2, 3];

`readonly T[]` removes mutating methods (push, pop, splice) and disallows index assignment. Use for function params to signal "I will not mutate this".

Tuple types — fixed-length, mixed types

let pair: [string, number] = ["age", 30];
let trio: [string, number, boolean] = ["x", 1, true];

// 命名元组(4.0+),仅文档用途
let coord: [x: number, y: number] = [10, 20];

// 可选元组元素
let opt: [string, number?] = ["x"];

// rest 元组元素
let rest: [string, ...number[]] = ["x", 1, 2, 3];

Tuples are fixed-length arrays with per-position types. Named labels (4.0+) document intent. `[string, number?]` allows trailing optional; `[T, ...U[]]` allows rest.

object — non-primitive type

let o: object = { x: 1 };
o = [1, 2, 3];    // ✅ 数组也是 object
o = () => {};     // ✅ 函数也是 object
// o = "hello";   // ❌ 字符串是 primitive

// ⚠️ object 上几乎什么也拿不到,你要的多半是 Record<string, unknown>
let m: Record<string, unknown> = { a: 1, b: "x" };

`object` means "anything not a primitive" — array, function, plain object all qualify. You can't access any properties on it; for typed dict use `Record<string, unknown>`.

enum — runtime + type

enum Color { Red, Green, Blue }   // 0, 1, 2

let c: Color = Color.Red;     // 0
Color[0];                       // "Red" (反查)

enum Status { Ok = "OK", Err = "ERR" }   // 字符串 enum

enum generates a runtime object with two-way mapping (number → name) for numeric enums. String enums are one-way. Usually prefer string literal unions for new code.

symbol — unique property keys

const id: symbol = Symbol("id");
const a = Symbol("x");
const b = Symbol("x");
a === b;   // false,每个 Symbol 都唯一

// unique symbol 才能当类型层面的常量键
const KEY: unique symbol = Symbol();
interface Box { [KEY]: number }

Every `Symbol()` call returns a unique value, so two symbols with the same description are not equal. Only a `const x: unique symbol` can be used as a computed property key in a type.

const enum — inlined, zero runtime

const enum Dir { Up, Down }

const d = Dir.Up;
// 编译输出直接内联为:  const d = 0;
// 不生成 Dir 这个运行时对象

// ⚠️ 与 isolatedModules / 单文件转译(Babel、esbuild)冲突

`const enum` is erased at compile time and its members are inlined as literals, so no runtime object is emitted. It clashes with `isolatedModules` and single-file transpilers like Babel.

Function type annotation

let add: (a: number, b: number) => number;
add = (x, y) => x + y;   // 参数类型自动推断

// 类型别名形式
type BinOp = (a: number, b: number) => number;
const mul: BinOp = (a, b) => a * b;

A function type `(a: T, b: U) => R` describes parameters and return type. Assigning a matching arrow lets parameter types be inferred contextually, so you skip re-annotating them.

Optional vs default parameters

function greet(name: string, greeting?: string) {
  return `${greeting ?? "Hi"}, ${name}`;   // greeting: string | undefined
}

function greet2(name: string, greeting = "Hi") {
  return `${greeting}, ${name}`;   // greeting: string,永远有值
}

`p?: T` makes a parameter `T | undefined`, so you must handle the missing case. A default `p = value` removes `undefined` from the type and fills it in when the arg is omitted.

Function overloads

function len(x: string): number;
function len(x: any[]): number;
function len(x: string | any[]): number {
  return x.length;
}

len("abc");     // ✅ number
len([1, 2, 3]); // ✅ number

Overload signatures declare multiple call shapes above one implementation. Callers see only the overloads; the implementation signature is hidden and must be compatible with all of them.

this parameter typing

interface Counter { count: number }

function inc(this: Counter, by: number) {
  this.count += by;   // this 已知是 Counter
}

const c: Counter = { count: 0 };
inc.call(c, 5);   // ✅ this 类型被检查

A fake first parameter named `this` types the receiver without appearing in the actual argument list. The compiler then checks `call` / `apply` / `bind` for a compatible `this`.

Interface vs type (12)

interface — object shape

interface User {
  id: number;
  name: string;
  email?: string;        // 可选属性
  readonly createdAt: Date;   // 只读
}

const u: User = { id: 1, name: "Lei", createdAt: new Date() };

Use interface for object shapes. `?` marks optional, `readonly` blocks reassignment. The everyday way to model entities and props.

type alias — anything, including unions

type ID = string | number;
type Point = { x: number; y: number };
type Callback = (err: Error | null, data?: string) => void;
type Tuple = [string, number];

type alias gives any type a name — unions, tuples, function types, primitives. interface cannot do unions; type alias can.

interface extends — inherit + extend

interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

// 多继承
interface Cat extends Animal, Pet {
  livesLeft: number;
}

interface can extend one or more interfaces. The child gets all parent props plus its own. Type errors fire at the declaration if props conflict.

type intersection — & to merge

type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged;   // { name: string; age: number }

const p: Person = { name: "Lei", age: 30 };

Use intersection `&` on type aliases to combine shapes — equivalent to interface extends but works with any type, not just interfaces.

Index signature — dynamic keys

interface StringMap {
  [key: string]: string;
}

const m: StringMap = {};
m["lang"] = "zh";
m["theme"] = "dark";

// 同时有已知键 + 动态键
interface Config {
  apiUrl: string;
  [key: string]: string;
}

Index signature lets you type a dict-like object with dynamic keys. All known keys must match the index signature's value type.

Call signature — callable interface

interface Greeter {
  (name: string): string;             // 可调用
  greeting: string;                    // 还有属性
}

const g: Greeter = ((name: string) => `${g.greeting}, ${name}`) as Greeter;
g.greeting = "Hello";
g("Lei");   // "Hello, Lei"

Interface can declare a call signature — describes a function that ALSO has properties. Useful for typing function-objects (React FCs with displayName, etc.).

interface vs type — which to pick

// ✅ 对象形状要被 extend / implement → interface
interface User { id: number; name: string }

// ✅ 联合 / 元组 / 映射 / 条件 → type
type ID = string | number;
type Tuple = [string, number];
type Keys<T> = keyof T;

Rule of thumb: interface for object shapes that other code extends or implements; type alias for unions, tuples, and any computed shape. Library public APIs lean interface; internal computed types lean type.

Construct signature — newable interface

interface UserCtor {
  new (id: number, name: string): { id: number; name: string };
}

function create(Ctor: UserCtor, id: number, name: string) {
  return new Ctor(id, name);
}

A `new (...) : T` signature describes a constructor type, so a value can be used with `new`. Useful when a factory takes a class reference and instantiates it.

Hybrid type — callable + indexable

interface Counter {
  (start: number): string;   // 可调用
  interval: number;           // 属性
  reset(): void;              // 方法
}

function make(): Counter {
  const c = ((start: number) => String(start)) as Counter;
  c.interval = 123;
  c.reset = () => {};
  return c;
}

One interface can combine a call signature, properties, and methods to model a function object. jQuery-style APIs (callable plus namespaced helpers) are the classic example.

interface implements on a class

interface Serializable {
  serialize(): string;
}

class User implements Serializable {
  constructor(public id: number) {}
  serialize() { return JSON.stringify({ id: this.id }); }
}

`class C implements I` makes the compiler verify the class has every member of `I`. It is a structural check only; it does not inherit implementations the way `extends` does.

Recursive type alias

type Json =
  | string
  | number
  | boolean
  | null
  | Json[]
  | { [key: string]: Json };

const data: Json = { a: 1, b: [true, null, { c: "x" }] };

A type alias may reference itself, which is how you model recursive shapes like JSON, trees, and nested menus. Each branch of the union can recurse independently.

Optional method vs optional property

interface Plugin {
  name: string;
  setup?(): void;              // 可选方法
  teardown?: () => void;       // 可选属性形式的方法
}

const p: Plugin = { name: "x" };
p.setup?.();      // 可选链调用
p.teardown?.();

Both `m?(): void` and `m?: () => void` make a method optional, so call them through `obj.m?.()`. The property form is contravariant under `strictFunctionTypes`; the shorthand is bivariant.

Union & intersection (9)

Union | — A or B

type StringOrNumber = string | number;

function format(x: string | number): string {
  if (typeof x === "string") return x.toUpperCase();
  return x.toFixed(2);
}

Union `A | B` accepts either type. You can only call methods/properties that exist on BOTH unless you narrow first.

Intersection & — A and B

type WithId = { id: string };
type WithName = { name: string };
type Entity = WithId & WithName;   // 同时有 id 和 name

const e: Entity = { id: "a1", name: "Lei" };

Intersection `A & B` requires all properties of A AND all properties of B. Useful for composing capabilities.

Discriminated union — tagged variants

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rect"; w: number; h: number }
  | { kind: "triangle"; base: number; h: number };

function area(s: Shape): number {
  switch (s.kind) {
    case "circle":   return Math.PI * s.radius ** 2;
    case "rect":     return s.w * s.h;
    case "triangle": return 0.5 * s.base * s.h;
  }
}

A common discriminator field (`kind` here) makes the union narrow-able via switch. The most reliable pattern for modeling variant data.

Exhaustive check with never

function area(s: Shape): number {
  switch (s.kind) {
    case "circle": return Math.PI * s.radius ** 2;
    case "rect":   return s.w * s.h;
    default:
      const _exhaustive: never = s;   // ✅ 漏掉一个分支这里直接报错
      throw new Error(`Unknown shape: ${_exhaustive}`);
  }
}

Assigning the unhandled value to `never` causes compile error if any union member is missing — catches "added a new variant, forgot to handle it" bugs.

Union of literals — enum-lite

type Status = "idle" | "loading" | "success" | "error";

let s: Status = "idle";
s = "loading";       // ✅
// s = "fetching";   // ❌ not in union

String literal unions are the modern alternative to enum — zero runtime cost, perfect autocomplete, can be JSON-serialized.

Optional chaining ?. on unions

type Maybe = { profile?: { name?: string } } | null;

function getName(u: Maybe): string | undefined {
  return u?.profile?.name;   // 任一环为空就短路返回 undefined
}

Optional chaining `?.` short-circuits to `undefined` as soon as the left side is `null` or `undefined`. The result type always includes `undefined`, which keeps it honest.

Nullish coalescing ?? vs ||

function port(input?: number) {
  const a = input ?? 8080;   // 只在 null/undefined 时用默认
  const b = input || 8080;   // 0 也会被替换掉!
  return [a, b];
}

port(0);   // [0, 8080]

`??` falls back only when the left side is `null` or `undefined`, while `||` also replaces `0`, `""`, and `false`. Use `??` for numeric and boolean defaults to avoid swallowing valid falsy values.

Union of function types

type Handler = ((e: string) => void) | ((e: number) => void);

// ⚠️ 调用联合函数时,参数类型取交集(never)
declare const h: Handler;
// h("x");   ❌ 参数被推为 string & number = never

// 多数时候你想要的是单个带联合参数的函数:
type Better = (e: string | number) => void;

Calling a union of function types requires arguments assignable to all members at once, so the parameter collapses to an intersection (often `never`). Usually you want one function taking a union parameter instead.

Narrowing a discriminated union by payload

type Result<T> =
  | { status: "ok"; data: T }
  | { status: "err"; message: string };

function unwrap<T>(r: Result<T>): T {
  if (r.status === "ok") return r.data;   // r.data 可用
  throw new Error(r.message);              // r.message 可用
}

Checking the discriminant (`status` here) narrows the union so the branch-specific payload becomes accessible. This is the backbone of typed Result and remote-data patterns.

Generics (14)

Generic function — single type parameter

function identity<T>(x: T): T {
  return x;
}

const s = identity("hello");   // T 推断为 string
const n = identity(42);        // T 推断为 number
const arr = identity<number[]>([1, 2, 3]);   // 显式传 T

Type parameter `<T>` makes the function work with any type while preserving the relationship between input and output. T is usually inferred — explicit only when inference fails.

Multiple type parameters

function pair<A, B>(a: A, b: B): [A, B] {
  return [a, b];
}

const p = pair("lei", 30);   // [string, number]

function map<T, U>(xs: T[], fn: (x: T) => U): U[] {
  return xs.map(fn);
}

Multiple type parameters keep relationships between args. `map<T, U>` ties the input array element type to the function param, and the function return to the output array element.

Generic constraint with extends

function longest<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}

longest("abc", "ab");          // ✅ string 有 length
longest([1, 2, 3], [1]);       // ✅ array 有 length
// longest(42, 99);            // ❌ number 没有 length

`T extends Shape` limits T to types matching Shape — you can use the constrained properties inside the body. Far better than `any` for "needs property X".

Default type parameter

interface ApiResponse<T = unknown> {
  data: T;
  status: number;
}

const a: ApiResponse = { data: "?", status: 200 };           // T = unknown
const b: ApiResponse<User> = { data: { id: 1 } as User, status: 200 };

Default `<T = X>` lets callers omit the type argument and fall back to the default. Common in framework code (React.Component<P = {}, S = {}>).

Generic constraint with keyof

function pluck<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { id: 1, name: "Lei" };
pluck(user, "id");      // number
pluck(user, "name");    // string
// pluck(user, "age");  // ❌ "age" 不是 user 的 key

K extends keyof T constrains K to a valid property name of T — return type T[K] gives the exact property type. This is what makes `_.pick` typesafe.

Generic type alias

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

const r: Result<number> = { ok: true, value: 42 };

Type aliases can take type parameters too. Common for result wrappers, container types, and computed shapes.

Generic interface

interface Cache<T> {
  get(key: string): T | undefined;
  set(key: string, value: T): void;
}

const userCache: Cache<User> = makeCache<User>();

Interface can carry type parameters. The instance fixes the parameter — `Cache<User>` has `get(key): User | undefined`.

Generic React component

interface ListProps<T> {
  items: T[];
  render: (item: T) => React.ReactNode;
}

function List<T>({ items, render }: ListProps<T>) {
  return <ul>{items.map((it, i) => <li key={i}>{render(it)}</li>)}</ul>;
}

<List items={users} render={(u) => u.name} />   // T 自动推断 User

Generic component preserves the item type across props — `render` gets the actual T, not `any`. The standard pattern for typed list components, tables, and selects.

Generic class

class Stack<T> {
  private items: T[] = [];
  push(x: T): void { this.items.push(x); }
  pop(): T | undefined { return this.items.pop(); }
  peek(): T | undefined { return this.items[this.items.length - 1]; }
}

const s = new Stack<number>();
s.push(1);
s.push(2);
s.pop();   // number | undefined

Classes take type parameters too. The parameter is bound when you instantiate (`new Stack<number>()`) and applies to all methods.

const type parameter (5.0+)

function asTuple<const T extends readonly unknown[]>(t: T): T {
  return t;
}

const a = asTuple([1, 2, 3]);   // readonly [1, 2, 3],不是 number[]
// const T 让推断像加了 as const 一样窄

A `const` type parameter (`<const T>`) infers the narrowest literal types from the argument, as if the caller wrote `as const`. It avoids widening tuples to arrays and literals to their base type.

Generic with multiple constraints

function merge<T extends object, U extends object>(a: T, u: U): T & U {
  return { ...a, ...u };
}

const r = merge({ id: 1 }, { name: "Lei" });
// r: { id: number } & { name: string }

Each type parameter can carry its own `extends` constraint, and the return type can combine them with `&`. This is how a typed `Object.assign`-style merge preserves both input shapes.

Generic default referencing earlier param

interface Reducer<S, A = { type: string }> {
  (state: S, action: A): S;
}

// A 默认 { type: string },也可显式传更精确的 action 联合
type CountReducer = Reducer<number>;

A later type parameter's default may reference an earlier one, e.g. `<S, A = S[]>`. Framework signatures use this so callers only specify the parameters they actually care about.

Generic constraint to a tuple length

function first<T extends readonly [unknown, ...unknown[]]>(t: T): T[0] {
  return t[0];
}

first([1, "a", true]);   // 1,类型 number
// first([]);            ❌ 空元组不满足约束

Constraining to `readonly [unknown, ...unknown[]]` requires at least one element, so indexing `t[0]` is provably safe. The compiler rejects an empty array at the call site.

Currying with generics

function prop<K extends string>(key: K) {
  return <T extends Record<K, unknown>>(obj: T): T[K] => obj[key];
}

const getName = prop("name");
getName({ name: "Lei", age: 30 });   // string

Returning a generic arrow from an outer function carries one type parameter into the inner scope. The inner function still infers `T` freshly at each call, keeping the helper reusable.

Utility types (22)

Partial<T> — all keys optional

interface User { id: number; name: string; email: string }

type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string }

function update(id: number, patch: Partial<User>) { /* ... */ }
update(1, { name: "Lei" });   // ✅ 只传一个属性

Partial<T> makes every property optional. Most common use: PATCH-style update functions that accept a subset of fields.

Required<T> — all keys required

interface Config { host?: string; port?: number }

type FullConfig = Required<Config>;
// { host: string; port: number }

function start(c: Required<Config>) { /* ... */ }

Required<T> strips all `?` modifiers. Use when you have a config with defaults filled in and want to assert "everything is now set".

Readonly<T> — freeze all keys

interface User { id: number; name: string }
type FrozenUser = Readonly<User>;

const u: FrozenUser = { id: 1, name: "Lei" };
// u.name = "Han";   // ❌ readonly

Readonly<T> marks every property `readonly`. Compile-time freeze — no runtime cost. For deep freeze, you need a recursive version.

Pick<T, K> — select keys

interface User { id: number; name: string; email: string; password: string }

type PublicUser = Pick<User, "id" | "name" | "email">;
// { id: number; name: string; email: string }

Pick<T, K> builds a new type from only the listed keys. The right choice for view models and DTOs derived from a richer entity.

Omit<T, K> — drop keys

interface User { id: number; name: string; password: string }

type SafeUser = Omit<User, "password">;
// { id: number; name: string }

Omit<T, K> is Pick's inverse — keep everything except the listed keys. Better than Pick when most fields stay.

Record<K, V> — typed dict

type Role = "admin" | "user" | "guest";

const permissions: Record<Role, string[]> = {
  admin: ["read", "write", "delete"],
  user: ["read", "write"],
  guest: ["read"],
};

Record<K, V> = { [P in K]: V } — an object with keys K and values V. With a literal union K, the compiler verifies every key is present.

Exclude<T, U> — filter union

type Status = "idle" | "loading" | "success" | "error";

type Resolved = Exclude<Status, "idle" | "loading">;
// "success" | "error"

Exclude<T, U> removes from T those members assignable to U — set difference on unions.

Extract<T, U> — intersect union

type All = string | number | boolean | null;

type Numerics = Extract<All, number | bigint>;
// number

Extract<T, U> keeps only the members of T assignable to U — set intersection on unions.

NonNullable<T> — strip null/undefined

type Maybe = string | number | null | undefined;

type Real = NonNullable<Maybe>;
// string | number

NonNullable<T> removes both null and undefined from a union. Common after narrowing with `x != null`.

ReturnType<F> — extract return type

function getUser() {
  return { id: 1, name: "Lei", email: "x@y.z" };
}

type User = ReturnType<typeof getUser>;
// { id: number; name: string; email: string }

ReturnType<F> reads the return type from a function type. Combined with `typeof fn`, lets you derive entity types from factories without restating the shape.

Parameters<F> — extract params tuple

function login(user: string, pwd: string, remember?: boolean) { /* ... */ }

type LoginArgs = Parameters<typeof login>;
// [user: string, pwd: string, remember?: boolean | undefined]

Parameters<F> returns a tuple of the function's param types. Useful for wrappers that need to forward the same argument list.

Awaited<T> — unwrap Promise

type T1 = Awaited<Promise<string>>;        // string
type T2 = Awaited<Promise<Promise<number>>>;  // number  (递归解)
type T3 = Awaited<number>;                    // number  (不是 Promise 就原样)

Awaited<T> recursively unwraps nested Promises. The official `await` semantics in the type system since 4.5.

ConstructorParameters<C>

class User {
  constructor(public id: number, public name: string) {}
}

type Args = ConstructorParameters<typeof User>;
// [id: number, name: string]

ConstructorParameters<C> extracts the constructor's parameter tuple. Use for factory functions that mirror a class's constructor.

InstanceType<C> — what `new C()` returns

class User { /* ... */ }

type U = InstanceType<typeof User>;
// User

InstanceType<C> gives the type of `new C()`. Mostly used with mixins and `typeof Class` to refer to the instance type.

Uppercase / Lowercase / Capitalize

type H = Uppercase<"hello">;     // "HELLO"
type W = Lowercase<"WORLD">;     // "world"
type T = Capitalize<"foo">;      // "Foo"
type U = Uncapitalize<"Bar">;    // "bar"

Built-in intrinsic types that transform string literals at the type level. Used to build typed event names, CSS-in-JS keys, etc.

OmitThisParameter<F> — drop this

function fn(this: { x: number }, y: number) {
  return this.x + y;
}

type Plain = OmitThisParameter<typeof fn>;
// (y: number) => number

OmitThisParameter<F> removes the `this` parameter from a function type, leaving the plain callable signature. Useful after `bind` removes the receiver requirement.

ThisParameterType<F> — extract this

function fn(this: { x: number }, y: number) {
  return this.x + y;
}

type Self = ThisParameterType<typeof fn>;
// { x: number }

ThisParameterType<F> pulls out the type of the `this` parameter declared on a function. Combine with `OmitThisParameter` when you wrap or rebind methods.

NoInfer<T> (5.4+) — block inference

function paint<C extends string>(
  palette: C[],
  fallback: NoInfer<C>,
) { /* ... */ }

paint(["red", "blue"], "red");    // ✅
// paint(["red", "blue"], "x");   ❌ "x" 不在 palette 推断出的 C 里

NoInfer<T> tells the compiler not to use that position when inferring the type parameter. So `C` is fixed by the first argument and the second is merely checked against it.

Record + Partial — sparse dictionary

type Lang = "en" | "zh" | "ja";

type Translations = Partial<Record<Lang, string>>;
const t: Translations = { en: "Hello" };   // zh / ja 可缺省

Wrapping `Record<K, V>` in `Partial` builds a dictionary where every key is optional, so you can fill a subset of a known key set. Common for partial i18n bundles and feature flags.

Pick + Required — selectively force keys

interface User { id?: number; name?: string; email?: string }

type WithId = User & Required<Pick<User, "id">>;
// id 必填,name / email 仍可选

const u: WithId = { id: 1 };   // ✅

Intersecting a type with `Required<Pick<T, K>>` forces just the chosen keys to be present while leaving the rest optional. Handy for "this field is now guaranteed" view models.

Mutable<T> — strip readonly (hand-rolled)

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

const frozen = { x: 1, y: 2 } as const;   // readonly { x: 1; y: 2 }
type Editable = Mutable<typeof frozen>;
// { x: 1; y: 2 },可改

TypeScript ships no built-in `Mutable`, so the `-readonly` mapped modifier is the canonical way to strip immutability. Pair it with `as const` data you later need to mutate.

Tuple to union via T[number]

const ROLES = ["admin", "user", "guest"] as const;

type Role = typeof ROLES[number];
// "admin" | "user" | "guest"

Indexing a readonly tuple type with `[number]` yields the union of its element types. This is the standard trick to derive a string-literal union from a single source-of-truth array.

Type narrowing (13)

typeof narrowing

function format(x: string | number): string {
  if (typeof x === "string") {
    return x.toUpperCase();   // x: string
  }
  return x.toFixed(2);        // x: number
}

`typeof x === "string" | "number" | "boolean" | "object" | "function" | "undefined" | "symbol" | "bigint"`. The compiler narrows the union inside the branch.

instanceof narrowing

class HttpError extends Error { status: number = 500 }
class NetError  extends Error { code: string = "ECONN" }

function handle(e: HttpError | NetError) {
  if (e instanceof HttpError) {
    console.log(e.status);   // e: HttpError
  } else {
    console.log(e.code);     // e: NetError
  }
}

`instanceof Class` narrows to the class type. Works for any constructor — both built-in (Error, Date) and user-defined classes.

in operator narrowing

type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(a: Fish | Bird) {
  if ("swim" in a) {
    a.swim();   // a: Fish
  } else {
    a.fly();    // a: Bird
  }
}

`"key" in obj` narrows to the union members that have the key. Lighter than discriminated unions when you don't want to add a tag field.

Equality narrowing

function example(x: string | null) {
  if (x !== null) {
    x.toUpperCase();   // x: string
  }
}

function check(a: "a" | "b", b: "b" | "c") {
  if (a === b) {
    // 这里 a 和 b 都是 "b",唯一公共值
  }
}

`===` and `!==` narrow union members. Comparing two unions narrows BOTH sides to the common members.

Truthy narrowing

function example(x: string | null | undefined) {
  if (x) {
    x.toUpperCase();   // x: string (null / undefined / "" 都过滤了)
  }
}

// 注意:空字符串也会被过滤
function trap(x: string | null) {
  if (x) {
    // x: string,但 "" 也被排除了!
  }
}

Truthy check `if (x)` narrows away null, undefined, 0, "", NaN, false. Watch out: empty string is also falsy, which is rarely what you want.

User-defined type guard with `is`

function isString(x: unknown): x is string {
  return typeof x === "string";
}

function example(x: unknown) {
  if (isString(x)) {
    x.toUpperCase();   // x: string
  }
}

Return type `x is T` tells the compiler "this function returns true iff x is T". The way to teach the type system about custom checks.

Assertion function with `asserts`

function assertString(x: unknown): asserts x is string {
  if (typeof x !== "string") throw new Error("not string");
}

function example(x: unknown) {
  assertString(x);
  x.toUpperCase();   // x: string  (assert 之后整段都是 string)
}

`asserts x is T` narrows x AFTER the call (not inside a branch). Useful when you want a guard that throws instead of returning a boolean.

Discriminated union switch — full pattern

type Action =
  | { type: "ADD"; payload: number }
  | { type: "RESET" }
  | { type: "SET"; payload: number };

function reduce(state: number, a: Action): number {
  switch (a.type) {
    case "ADD":   return state + a.payload;
    case "RESET": return 0;
    case "SET":   return a.payload;
    default:
      const _: never = a;   // 漏一个分支编译报错
      return state;
  }
}

The full Redux-style discriminated-union reducer pattern with exhaustiveness check. Adding a new action type without handling it fails compilation.

Array.isArray narrowing

function flatten(x: string | string[]): string {
  if (Array.isArray(x)) {
    return x.join(", ");   // x: string[]
  }
  return x;                 // x: string
}

`Array.isArray(x)` is a built-in type guard that narrows `T | T[]` down to the array branch. It is the reliable way to accept "one or many" arguments.

Discriminant on a literal property

type Resp =
  | { ok: true; body: string }
  | { ok: false; code: number };

function read(r: Resp) {
  if (r.ok) return r.body;   // ok: true 分支
  return `error ${r.code}`; // ok: false 分支
}

A boolean (or any literal) field works as a discriminant just like a string `kind`. Checking `r.ok` narrows the union without needing a dedicated tag string.

Narrowing with const assertion

function handle(method: string) {
  const m = method.toUpperCase();
  if (m === "GET" || m === "POST") {
    const verb = m as "GET" | "POST";   // 明确收窄
    return verb;
  }
}

After comparing a widened `string` against literals, an `as` assertion locks it to the literal union for downstream use. Prefer typing the source narrowly, but this rescues already-widened values.

Control-flow narrowing after assignment

let x: string | number;
x = "hello";
x.toUpperCase();   // x: string,赋值后立即收窄

x = 42;
x.toFixed(2);      // x: number

Assigning a value narrows a variable to that value's type for the following statements, even when the declared type is a union. This is the everyday flow analysis you rely on without noticing.

Narrowing object property via local const

function run(cfg: { value?: number }) {
  // cfg.value 每次访问可能被别处改,TS 不替你收窄
  const v = cfg.value;
  if (v !== undefined) {
    v.toFixed(2);   // ✅ 局部常量收窄成立
  }
}

Narrowing on `obj.prop` can be invalidated by intervening calls, so copy it into a `const` first and narrow that. The local binding is provably stable, so the guard sticks.

Mapped types (11)

Basic mapped type

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

interface User { id: number; age: number }
type UserStrings = Stringify<User>;
// { id: string; age: string }

`{ [K in keyof T]: ... }` iterates over T's keys and produces a new type. The foundation of Partial, Required, Readonly — they're all mapped types.

Mapped with value transform

type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

interface User { id: number; name: string }
type N = Nullable<User>;
// { id: number | null; name: string | null }

Reference the original value type via `T[K]` and transform it (here, union with null). Common for "all fields optional/nullable" wrappers.

Key remapping with `as` (4.1+)

type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

interface User { id: number; name: string }
type UserGetters = Getters<User>;
// { getId: () => number; getName: () => string }

The `as` clause renames keys during mapping. Combined with template literal types, generates getter / setter / event-name shapes from a base type.

Filter keys with as never

type PickStrings<T> = {
  [K in keyof T as T[K] extends string ? K : never]: T[K];
};

interface Mixed { name: string; age: number; role: string }
type Strings = PickStrings<Mixed>;
// { name: string; role: string }

Remap a key to `never` to drop it from the result. Combined with conditional types, lets you filter object properties by value type.

Modifier + and - on optional / readonly

type Mutable<T> = { -readonly [K in keyof T]: T[K] };
type Optional<T> = { [K in keyof T]+?: T[K] };
type Required<T> = { [K in keyof T]-?: T[K] };

interface Frozen { readonly id: number; readonly name: string }
type Editable = Mutable<Frozen>;
// { id: number; name: string }

Use `-` to remove an `optional` or `readonly` modifier, `+` to add (default). The mechanism behind built-in Required and Mutable utilities.

DeepReadonly with mapped + recursion

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? T[K] extends Function
      ? T[K]
      : DeepReadonly<T[K]>
    : T[K];
};

interface Tree { left: Tree | null; value: number }
type FrozenTree = DeepReadonly<Tree>;
// 整棵树都是 readonly

Recursive mapped type freezes nested object properties too. Exclude Function (typeof function === "object") to avoid mangling methods.

Mapped to ensure all keys present

type FormValues<Fields extends string> = {
  [K in Fields]: string;
};

type LoginForm = FormValues<"email" | "password">;
// { email: string; password: string }

const f: LoginForm = { email: "x@y.z", password: "secret" };
// 漏 email 编译报错

Iterate over a finite string union to require every key. Compiler verifies completeness — useful for form schemas, permission tables, route maps.

Homomorphic mapped type preserves modifiers

type Clone<T> = { [K in keyof T]: T[K] };

interface User { readonly id: number; name?: string }
type Copy = Clone<User>;
// { readonly id: number; name?: string },readonly / ? 都保留

A mapped type written as `[K in keyof T]` is homomorphic, so it copies each property's `readonly` and `?` modifiers automatically. That is why `Partial`/`Readonly` keep the original structure.

Record via mapped over a union

type MyRecord<K extends keyof any, V> = {
  [P in K]: V;
};

type Flags = MyRecord<"a" | "b", boolean>;
// { a: boolean; b: boolean }

Mapping `[P in K]` over a union of keys is exactly how the built-in `Record<K, V>` is defined. `keyof any` (`string | number | symbol`) is the broadest valid key constraint.

Mapped type value lookup with conditional

type Boxed<T> = {
  [K in keyof T]: T[K] extends string ? { text: T[K] } : { value: T[K] };
};

interface M { name: string; age: number }
type R = Boxed<M>;
// { name: { text: string }; age: { value: number } }

Inside a mapped type you can branch on `T[K]` with a conditional to transform values per their type. Useful for wrapping fields into tagged containers based on their original type.

Mapped type over array / tuple

type Boxed<T> = { [K in keyof T]: { v: T[K] } };

type R = Boxed<[number, string]>;
// [{ v: number }, { v: string }],元组结构保留

Mapping over a tuple keeps it a tuple of the same length, transforming each positional element. Array/tuple-aware mapped types power utilities like a per-element wrapper.

Conditional types (10)

Basic conditional type — extends ? :

type IsString<T> = T extends string ? "yes" : "no";

type A = IsString<"hello">;   // "yes"
type B = IsString<42>;         // "no"
type C = IsString<string>;     // "yes"

`T extends U ? X : Y` chooses a type based on assignability. The base mechanism for all the smart utility types.

Distributive conditional type

type ToArray<T> = T extends any ? T[] : never;

type A = ToArray<string | number>;
// string[] | number[]  (而不是 (string | number)[])
//
// 联合传入裸类型参数时,条件类型会分配到每个成员

When the type parameter is "naked" (just `T`, not wrapped), the conditional type distributes over each union member. To prevent distribution, wrap: `[T] extends [U]`.

Wrap to prevent distribution

type IsUnion<T, _T = T> = T extends any
  ? [Exclude<_T, T>] extends [never]
    ? false
    : true
  : never;

type A = IsUnion<string | number>;   // true
type B = IsUnion<string>;             // false

Wrap a type parameter in a tuple `[T]` to prevent distributive behavior. Pattern used to test "is this exactly a union?".

Conditional type chain

type TypeName<T> =
  T extends string ? "string" :
  T extends number ? "number" :
  T extends boolean ? "boolean" :
  T extends undefined ? "undefined" :
  T extends Function ? "function" :
  "object";

type A = TypeName<"hello">;   // "string"
type B = TypeName<() => void>;// "function"

Chain conditionals to mimic switch / pattern matching. Reads top to bottom — first match wins.

Conditional with constraint

type NonEmpty<T extends string> = T extends "" ? never : T;

type A = NonEmpty<"hello">;   // "hello"
type B = NonEmpty<"">;         // never

function tag<T extends string>(s: NonEmpty<T>): T {
  return s;
}
// tag("");  ❌ "" 推断为 never,无法调用

Combine generic constraint with a conditional to validate input shape at call site. `NonEmpty<"">` resolves to `never`, blocking the call.

NonNullable — built-in conditional

// 内置实现:
type NonNullable<T> = T extends null | undefined ? never : T;

type A = NonNullable<string | null>;       // string
type B = NonNullable<string | undefined>;  // string
type C = NonNullable<null>;                // never

NonNullable is implemented as a distributive conditional that maps null/undefined to never. Reading built-in utility implementations is the best way to grok conditional types.

Flatten one array level

type Flatten<T> = T extends (infer E)[] ? E : T;

type A = Flatten<string[]>;   // string
type B = Flatten<number>;     // number(不是数组就原样)

A conditional with `infer E` peels one array level off when `T` is an array, and returns `T` unchanged otherwise. The same shape underlies the standard library's `FlatArray`.

Recursive deep-flatten conditional

type DeepFlatten<T> = T extends (infer E)[] ? DeepFlatten<E> : T;

type A = DeepFlatten<number[][][]>;   // number
type B = DeepFlatten<string>;         // string

Recursing into the `infer E` branch flattens nested arrays of any depth down to the leaf type. Recursive conditionals are the type-level equivalent of a `while` loop.

Conditional inside a generic default

type Unbox<T, F = T extends Promise<infer U> ? U : T> = F;

type A = Unbox<Promise<number>>;   // number
type B = Unbox<string>;            // string

A default type parameter can itself be a conditional that computes from earlier parameters. It lets you expose a derived type while keeping the public signature short.

Function vs value with conditional

type Unwrap<T> = T extends () => infer R ? R : T;

type A = Unwrap<() => number>;   // number
type B = Unwrap<string>;          // string

Inferring the return type of a no-arg function and falling back to the value itself models a "lazy or eager" input. Common for config options that accept either a value or a factory.

infer (10)

infer — extract function return type

type MyReturnType<T> = T extends (...args: any) => infer R ? R : never;

type R1 = MyReturnType<() => string>;            // string
type R2 = MyReturnType<(x: number) => boolean>;  // boolean

infer R binds a name to a type position you want to extract. The built-in ReturnType uses exactly this pattern.

infer — unwrap Promise

type MyAwaited<T> = T extends Promise<infer U> ? U : T;

type A = MyAwaited<Promise<string>>;   // string
type B = MyAwaited<number>;             // number

Use infer inside Promise<...> to extract the resolved type. The built-in Awaited recurses to handle nested Promises.

infer — array element type

type Element<T> = T extends (infer E)[] ? E : never;

type A = Element<number[]>;            // number
type B = Element<Array<{ x: number }>>;// { x: number }

Pattern `(infer E)[]` extracts the element type from an array. The same as the lookup `T[number]` for arrays.

infer — tuple head / tail

type Head<T extends any[]> = T extends [infer H, ...any[]] ? H : never;
type Tail<T extends any[]> = T extends [any, ...infer R] ? R : [];

type H = Head<[string, number, boolean]>;   // string
type T = Tail<[string, number, boolean]>;   // [number, boolean]

Combine variadic tuple types with infer to deconstruct tuples — head, tail, last, init. Foundation of compile-time list manipulation.

infer — first function parameter

type FirstParam<F> = F extends (first: infer P, ...rest: any[]) => any ? P : never;

type A = FirstParam<(name: string, age: number) => void>;   // string

Use infer in the first position of the parameter list, ignore the rest. Mirrors the built-in Parameters, narrowed to just the head.

infer — last tuple element

type Last<T extends any[]> = T extends [...any[], infer L] ? L : never;

type A = Last<[1, 2, 3]>;   // 3
type B = Last<[]>;           // never

A rest pattern `[...any[], infer L]` captures the final element of a tuple. Variadic tuple positions plus `infer` let you read head, tail, and last without recursion.

infer — Promise resolved type (recursive)

type Resolved<T> = T extends Promise<infer U> ? Resolved<U> : T;

type A = Resolved<Promise<Promise<number>>>;   // number
type B = Resolved<string>;                       // string

Recursing on the inferred `U` unwraps arbitrarily nested promises down to the eventual value, mirroring the built-in `Awaited`. The non-promise case stops the recursion.

infer with constraint (extends)

type FirstString<T> =
  T extends [infer S extends string, ...any[]] ? S : never;

type A = FirstString<["hi", 1, 2]>;   // "hi"
type B = FirstString<[1, 2]>;          // never

Since 4.7 you can constrain an inferred type with `infer S extends C`, so the branch only matches when the inferred type fits. It avoids a separate nested conditional to validate the inference.

infer — object property type

type PropType<T, K extends string> =
  T extends { [P in K]: infer V } ? V : never;

type A = PropType<{ id: number; name: string }, "id">;   // number

Placing `infer V` in a property position extracts that field's type from a matching object shape. This generalizes indexed access `T[K]` to patterns the compiler can match structurally.

infer — rest parameters tuple

type RestArgs<F> =
  F extends (first: any, ...rest: infer R) => any ? R : never;

type A = RestArgs<(id: number, a: string, b: boolean) => void>;
// [a: string, b: boolean]

Inferring `...rest: infer R` captures every parameter after the first as a tuple. Useful for wrappers that fix the first argument and forward the remainder.

Template literal (8)

Template literal type — basic

type Greeting = `hello ${string}`;

const g1: Greeting = "hello world";   // ✅
const g2: Greeting = "hello there";   // ✅
// const g3: Greeting = "hi";         // ❌ 不以 "hello " 开头

Template literal types (4.1+) let you constrain strings by pattern. Embedding `string` makes that part variable; embedding a literal union makes it a finite set.

Cross product with union

type Lang = "en" | "zh";
type Kind = "button" | "link";

type Key = `${Kind}.${Lang}`;
// "button.en" | "button.zh" | "link.en" | "link.zh"

Embedding a union in a template produces the cross product. Used for i18n keys, event names, CSS class names.

Template literal in mapped type

type EventNames<T> = {
  [K in keyof T as `on${Capitalize<string & K>}`]?: (value: T[K]) => void;
};

interface Form { name: string; age: number }
type FormHandlers = EventNames<Form>;
// { onName?: (v: string) => void; onAge?: (v: number) => void }

Combine mapped types + key remapping + template literals to generate handler types. The pattern behind type-safe event APIs.

String parsing with infer

type Split<S extends string, D extends string> =
  S extends `${infer A}${D}${infer B}`
    ? [A, ...Split<B, D>]
    : [S];

type A = Split<"a,b,c,d", ",">;   // ["a", "b", "c", "d"]

Recursive template literal + infer can split strings, parse routes, validate string shapes — all at compile time.

Template literal coerces unions to string

type Id = `user_${number}`;
const a: Id = "user_42";   // ✅
// const b: Id = "user_x"; ❌ 必须是数字

type Hex = `#${string}`;
const c: Hex = "#ff0000";  // ✅

Embedding `number` or `string` in a template type constrains the shape while leaving that segment free. Great for branded id formats, hex colors, and prefixed keys.

Uppercase inside template literal

type Method = "get" | "post";
type Route = `${Uppercase<Method>} /api`;
// "GET /api" | "POST /api"

Intrinsic string types like `Uppercase` compose directly inside template literals, transforming each union member as the cross product is built. Handy for HTTP method tables and event channels.

Extract prefix with template + infer

type RemovePrefix<S extends string, P extends string> =
  S extends `${P}${infer Rest}` ? Rest : S;

type A = RemovePrefix<"on:click", "on:">;   // "click"
type B = RemovePrefix<"click", "on:">;       // "click"

Matching `\`${P}${infer Rest}\`` strips a known prefix and captures whatever follows. Use it to parse event names, namespaced keys, or CSS variable prefixes at the type level.

Join tuple of strings with separator

type Join<T extends string[], D extends string> =
  T extends [infer F extends string, ...infer R extends string[]]
    ? R extends []
      ? F
      : `${F}${D}${Join<R, D>}`
    : "";

type A = Join<["a", "b", "c"], "/">;   // "a/b/c"

Recursive template literals concatenate a tuple of strings with a separator, the type-level inverse of `Split`. The empty-rest case avoids a trailing separator.

Assertions (10)

as — type assertion (cast)

const el = document.getElementById("input") as HTMLInputElement;
el.value = "hello";   // ✅ 编译器信你说的是 input

// ❌ 不检查,错了运行时炸
const wrong = "hello" as unknown as number;

`as Foo` is an UNCHECKED cast — the compiler trusts you. Use only when you genuinely know more than the compiler. Never as a "shut up the type error" hammer.

as const — freeze literal

const a = "hello";              // type: string
const b = "hello" as const;     // type: "hello"

const arr1 = [1, 2, 3];         // type: number[]
const arr2 = [1, 2, 3] as const;// type: readonly [1, 2, 3]

const obj = { x: 1, y: 2 } as const;
// type: { readonly x: 1; readonly y: 2 }

`as const` freezes a value to its narrowest type — string stays as the literal, array becomes readonly tuple, object becomes deeply readonly. Use on lookup tables and configs.

satisfies — validate without widening (4.9+)

type Config = Record<string, string | number>;

const config = {
  port: 8080,
  host: "localhost",
  protocol: "https",
} satisfies Config;

config.port;       // number  (推断保留)
config.protocol;   // "https" (字面量保留)

// ❌ 用 : Config 标注会放宽到 string | number 丢失字面量

`satisfies` verifies a value matches a type WITHOUT widening the inferred type. Best for configs where you want both compile-time validation and narrow literal types for lookups.

Non-null assertion !

const el = document.getElementById("input")!;  // 断言不是 null
el.click();

// ❌ 滥用会埋雷
// 更安全:
const el2 = document.getElementById("input");
if (el2) el2.click();

`!` postfix tells the compiler "I promise this is not null/undefined". DOM lookups are the main legit use case — and even there, prefer an explicit null check.

Angle-bracket cast — legacy syntax

const a = <string>someValue;       // 旧写法
const b = someValue as string;     // ✅ 推荐(JSX 不冲突)

Old `<Type>value` cast conflicts with JSX. Always use `as Type` in TSX files (and prefer it everywhere for consistency).

Double assertion via unknown

// ❌ 直接断不让你过
// const n = "hello" as number;

// ✅ 经过 unknown 二段断言(编译器允许,但你自己要负责)
const n = "hello" as unknown as number;

// 真要这么干说明设计有问题,重新想想类型

When source and target types overlap insufficiently, TypeScript blocks a direct cast. `as unknown as Foo` is the escape hatch — almost always a red flag that the types are wrong.

satisfies + as const together

type Route = { path: string; auth: boolean };

const routes = {
  home: { path: "/", auth: false },
  admin: { path: "/admin", auth: true },
} as const satisfies Record<string, Route>;

routes.home.path;   // "/"(字面量保留)

Chaining `as const satisfies T` freezes the value to its narrowest literal type and validates it against `T` in one step. You get both deep-readonly literals and shape checking.

const assertion on object literal

function on<E extends string>(event: E) { return event; }

on("click");                 // E 推断为 string
on("click" as const);        // E 推断为 "click"

const ev = { type: "click" } as const;
ev.type;   // "click",不是 string

Applying `as const` to a literal or object freezes properties to their exact values and prevents widening at the call site. It is the lightweight way to pass precise literals into generics.

Definite assignment assertion !

let value!: number;   // 告诉编译器:我保证用前会赋值

function init() { value = 42; }
init();
value.toFixed(2);   // ✅ 不再报 used before assigned

class C {
  ref!: HTMLElement;   // 类字段同理(由 DI / 生命周期赋值)
}

A `!` after a variable or class field name is the definite assignment assertion, telling the compiler a value will be assigned before use. Common for fields set by DI, lifecycle hooks, or test setup.

const assertion for readonly tuple args

function dispatch(...args: readonly [string, number]) {}

const payload = ["save", 1] as const;
dispatch(...payload);   // ✅ as const 让它是 readonly ["save", 1]
// 不加 as const 会推成 (string | number)[],spread 不匹配

Spreading an array into a tuple parameter needs the array typed as a fixed-length tuple, which `as const` provides. Without it the array widens to `T[]` and the spread fails to match positions.

Declaration merging (7)

Interface declaration merging

interface User { id: number }
interface User { name: string }

// 自动合并:
// interface User { id: number; name: string }

const u: User = { id: 1, name: "Lei" };

Two interfaces with the same name in the same scope merge automatically. Used by libraries to let users extend their types.

Module augmentation

// my-app.d.ts
import "express";

declare module "express" {
  interface Request {
    user?: { id: number; name: string };
  }
}

// 现在 Express 的 Request 上有了 user 字段
app.use((req, res, next) => {
  req.user = { id: 1, name: "Lei" };
  next();
});

`declare module "name"` reopens an external module to add types. The standard way to extend Express's Request, Vue's ComponentCustomProperties, etc.

Global declaration

// globals.d.ts
declare global {
  interface Window {
    myApp: { version: string };
  }

  var __DEV__: boolean;
}

export {};   // 重要:让这个文件成为 module

// 使用
window.myApp = { version: "1.0.0" };
if (__DEV__) console.log("dev mode");

`declare global { ... }` inside a module adds to the global scope. The trailing `export {}` is required to make the file a module. Used for build-time defines and Window augmentation.

namespace + function merging

function area(r: number) { return Math.PI * r * r; }

namespace area {
  export const unit = "cm²";
}

area(2);        // 函数调用
area.unit;      // "cm²",挂在同名 namespace 上

A function and a same-named `namespace` merge so you can attach static helpers and constants to a function. This is how libraries expose `fn` plus `fn.helper` with full typing.

enum + namespace merging

enum Color { Red, Green, Blue }

namespace Color {
  export function hex(c: Color): string {
    return ["#f00", "#0f0", "#00f"][c];
  }
}

Color.hex(Color.Red);   // "#f00"

A `namespace` with the same name as an `enum` merges, letting you attach helper functions to the enum object. The enum stays a value and a type while gaining methods.

Ambient declaration with declare

// 描述一个由 <script> 注入、没有类型的全局
declare const ANALYTICS_ID: string;
declare function track(event: string): void;

track("page_view");   // ✅ 有类型,无运行时代码

`declare` introduces a type-only declaration for something that exists at runtime but has no TypeScript source, like a script-injected global. It emits no JavaScript.

Augment an existing global interface

// env.d.ts
declare global {
  interface ImportMetaEnv {
    readonly VITE_API_URL: string;
  }
}
export {};

import.meta.env.VITE_API_URL;   // ✅ 有类型

Reopening a global interface inside `declare global` adds fields to existing ambient types, like Vite's `ImportMetaEnv`. The trailing `export {}` keeps the file a module so augmentation is scoped correctly.

Decorators (6)

Legacy class decorator (experimental)

// tsconfig: { "experimentalDecorators": true }

function logged<T extends { new(...args: any[]): {} }>(Cls: T) {
  return class extends Cls {
    constructor(...args: any[]) {
      console.log("creating", Cls.name);
      super(...args);
    }
  };
}

@logged
class User {
  constructor(public name: string) {}
}

new User("Lei");   // logs "creating User"

Old decorator syntax requires `experimentalDecorators: true` in tsconfig. Still widely used in Angular, NestJS, TypeORM.

Stage 3 standard decorator (5.0+)

// 不需要 experimentalDecorators
function logged<C extends new (...args: any[]) => any>(
  Cls: C,
  ctx: ClassDecoratorContext,
) {
  return class extends Cls {
    constructor(...args: any[]) {
      console.log("creating", ctx.name);
      super(...args);
    }
  };
}

@logged
class User {
  constructor(public name: string) {}
}

TypeScript 5.0 implements the Stage 3 ECMAScript decorator proposal — different shape than legacy. Receives a `context` object. New code should target this.

Method decorator

// Stage 3
function timed(
  fn: Function,
  ctx: ClassMethodDecoratorContext,
) {
  return function (this: unknown, ...args: any[]) {
    const t0 = performance.now();
    const result = fn.apply(this, args);
    console.log(`${String(ctx.name)} took ${performance.now() - t0}ms`);
    return result;
  };
}

class Api {
  @timed
  fetch() { /* ... */ }
}

Method decorator wraps or replaces a method. The context tells you the method name, whether it's static, and access metadata.

Accessor decorator (Stage 3)

function logged<T>(
  target: { get: () => T },
  ctx: ClassGetterDecoratorContext,
) {
  return function (this: unknown) {
    console.log("read", String(ctx.name));
    return target.get.call(this);
  };
}

class Box {
  #v = 1;
  @logged get value() { return this.#v; }
}

A getter decorator receives `{ get }` plus a `ClassGetterDecoratorContext` and may return a replacement accessor. Stage 3 decorators have distinct context types per target kind.

Field decorator with initializer

function double(
  _target: undefined,
  ctx: ClassFieldDecoratorContext,
) {
  return function (initial: number) {
    return initial * 2;   // 改写初始值
  };
}

class C {
  @double count = 5;   // 实例化后 count === 10
}

A Stage 3 field decorator returns an initializer function that receives the original value and returns the stored one. It runs per instance, so it can transform or validate the initial field value.

addInitializer for auto-bind

function bound(
  fn: Function,
  ctx: ClassMethodDecoratorContext,
) {
  ctx.addInitializer(function (this: any) {
    this[ctx.name] = fn.bind(this);   // 实例化时绑定 this
  });
}

class Btn {
  label = "ok";
  @bound onClick() { return this.label; }
}

The Stage 3 context exposes `addInitializer`, a callback that runs during construction. Binding the method to `this` there gives the classic auto-bind behavior without a constructor.

Common pitfalls (17)

any vs unknown — the most common trap

// ❌ any 把检查全关了,错误悄悄溜进运行时
function badParse(input: string): any {
  return JSON.parse(input);
}
const data = badParse("{}");
data.foo.bar.baz();   // 不报错,运行时 TypeError

// ✅ unknown 强制你先收窄
function goodParse(input: string): unknown {
  return JSON.parse(input);
}
const safe = goodParse("{}");
// safe.foo;  ❌ 不让你动
if (typeof safe === "object" && safe !== null && "foo" in safe) {
  // 这里才安全
}

any disables type checking; unknown forces narrowing. For "value of unknown shape" (parsed JSON, third-party callbacks), use unknown — any is the wrong default 99% of the time.

Non-null assertion ! abuse

// ❌ 滥用 ! 把编译期检查关了
const el = document.getElementById("missing")!;
el.click();   // 运行时 Cannot read 'click' of null

// ✅ 优先显式 null 检查
const el2 = document.getElementById("input");
if (el2) el2.click();

// ✅ 优先可选链
el2?.click();

Postfix `!` makes a value non-null at compile time only — runtime is unchanged. Wrong assertion = production crash. Prefer `if (x)` or `x?.method()`.

type vs interface — choosing wrong

// ❌ 库的公共 API 用 type,消费者不能 declare merge 扩展
type LibUser = { id: number; name: string };

// ✅ 库的公共 API 用 interface
interface LibUser2 {
  id: number;
  name: string;
}

// 消费方:
declare module "lib" {
  interface LibUser2 {
    customField?: string;
  }
}

For library public APIs that consumers may augment, use interface (declaration merging works). For unions, tuples, mapped, conditional types, you NEED type alias.

Function parameter variance

// 默认 method 形式参数是 bivariant,不严格
interface Listener {
  onEvent(e: Event): void;
}
const l: Listener = {
  onEvent(e: MouseEvent) {}   // ✅ 默认允许(不安全)
};

// 开 strictFunctionTypes 后 function 形式参数严格逆变
type Listener2 = {
  onEvent: (e: Event) => void;
};
const l2: Listener2 = {
  // onEvent: (e: MouseEvent) => {}   // ❌ 报错
  onEvent: (e: Event) => {}            // ✅
};

Method shorthand (`f(): T`) is bivariant; function property (`f: () => T`) is contravariant under strictFunctionTypes. Use function property form for safer event handler types.

enum runtime cost vs const enum vs string union

// ❌ enum 会生成运行时代码(双向映射对象)
enum Color { Red, Green, Blue }

// ⚠️ const enum 内联,没运行时代码,但与 isolatedModules / babel 冲突
const enum Status { Ok, Err }

// ✅ 字符串字面量联合:零运行时、JSON 友好、自动补全完美
type ColorLit = "red" | "green" | "blue";

Regular enums emit a runtime object (extra bytes). const enum inlines but breaks isolatedModules. String literal unions are the modern recommendation — zero cost, JSON-friendly, perfect autocomplete.

as Foo unsafe cast hides errors

// ❌ as 是不检查的强转
const x = "hello" as unknown as number;
const y = x.toFixed(2);   // 编译过,运行时 TypeError

// ✅ 用 satisfies 校验
const config = {
  port: 8080,
  host: "localhost",
} satisfies Record<string, string | number>;

// ✅ 真不得已用 as,加注释说明原因
// DOM 保证返回 input,querySelector 不知道
const input = document.querySelector(".x") as HTMLInputElement;

`as` bypasses the compiler. Use satisfies for validation; use `as` only when you genuinely know more than the compiler (DOM lookups), and document the reason.

Object spread loses required keys

interface User { id: number; name: string }

function merge(base: Partial<User>, override: Partial<User>): User {
  // ❌ TS 不知道合并后必填字段都齐了
  // return { ...base, ...override };

  // ✅ 保证必填字段
  return {
    id: override.id ?? base.id ?? 0,
    name: override.name ?? base.name ?? "",
    ...base,
    ...override,
  };
}

Spreading two Partial<T> gives Partial<T> back — the compiler can't prove required fields are present. Provide explicit fallbacks for required keys before spreading.

void return widening

// 参数位置的 void 返回,接受任意返回值
type Handler = (e: Event) => void;

const h: Handler = (e) => 42;   // ✅ 合法,返回值被丢弃

// 这是有意为之,让 Array.prototype.forEach 等接受 (x => doSomething())
[1, 2, 3].forEach((x) => x + 1);   // x + 1 返回 number,但 forEach 期望 void

Function parameter with `void` return type accepts ANY return. Designed so callbacks like forEach (which expects `void`) can accept any expression-bodied arrow. Quirk to know, not necessarily a bug.

Excess property check only on fresh literals

interface Opts { width: number }

// ❌ 直接传字面量触发多余属性检查
// draw({ width: 10, height: 20 });

// ⚠️ 经过变量就绕过了检查(结构兼容)
const o = { width: 10, height: 20 };
draw(o);   // ✅ 不报错

function draw(_: Opts) {}

Excess property checks fire only when you pass an object literal directly. Assigning to a variable first relaxes to structural compatibility, so the extra property slips through silently.

Array index access is not undefined-checked

const xs = [1, 2, 3];
const x = xs[10];   // 类型是 number,但运行时是 undefined!

// ✅ 开 noUncheckedIndexedAccess 后:
// x 的类型变成 number | undefined

By default `arr[i]` is typed as the element type even for out-of-range indices, hiding real `undefined`s. Enable `noUncheckedIndexedAccess` to make index access return `T | undefined`.

Empty object type {} is too permissive

// {} 不是"空对象",而是"除 null/undefined 外的任何值"
let a: {} = 42;        // ✅
let b: {} = "x";       // ✅
let c: {} = [1, 2];    // ✅
// let d: {} = null;   ❌

// 想表达"任意非空对象"用 Record<string, unknown> 或 object

The type `{}` means "any value except null and undefined", so it accepts numbers and strings, not just objects. For an actual object use `object` or `Record<string, unknown>`.

Forgotten await on a Promise condition

async function check() {
  // ❌ Promise 永远是 truthy,分支必走
  if (isReady()) { /* 总是进来 */ }

  // ✅ 别忘了 await
  if (await isReady()) { /* 正确 */ }
}
declare function isReady(): Promise<boolean>;

A `Promise` object is always truthy, so an un-awaited promise in an `if` always takes the branch. Enable `no-misused-promises` (typescript-eslint) to catch this class of bug.

Comparing with == triggers coercion

// == 会做类型转换,结果反直觉
// 0 == ""        // true
// null == undefined  // true
// [] == false    // true

// ✅ 永远用 ===
if (value === 0) { /* ... */ }

Loose `==` performs implicit coercion, producing surprising equalities like `0 == ""`. Always use strict `===`; the `eqeqeq` lint rule enforces it across a codebase.

Return type widening on object literals

// 返回值被推宽成 string,丢了字面量
function makeBad() {
  return { status: "ok" };   // { status: string }
}

// ✅ as const 或显式返回类型保留字面量
function makeGood() {
  return { status: "ok" } as const;   // { readonly status: "ok" }
}

A returned object literal widens its property types (`"ok"` becomes `string`), which breaks downstream discriminated-union narrowing. Use `as const` or an explicit return type to keep the literals.

unknown in catch needs narrowing

try {
  risky();
} catch (e) {
  // e: unknown(useUnknownInCatchVariables 默认开)
  // e.message;   ❌ 不能直接访问
  if (e instanceof Error) {
    console.log(e.message);   // ✅ 收窄后可用
  }
}

Since 4.4 a `catch` variable is typed `unknown`, because anything can be thrown, not just `Error`. Narrow with `instanceof Error` before touching `.message` or `.stack`.

Optional chaining swallows real errors

// ❌ 过度可选链把本不该缺的字段也容忍了
const name = response?.data?.user?.name ?? "anon";

// 如果 data 一定存在,缺了应该报错而不是默默 "anon"
// ✅ 只在真正可空的环节用 ?.
const name2 = response.data.user?.name ?? "anon";

Chaining `?.` through fields that should always exist hides structural bugs behind a silent fallback. Apply optional chaining only at genuinely nullable links, not as a blanket guard.

Index signature hides typos as any

interface Dict { [k: string]: number }

const d: Dict = { count: 1 };
d.conut;   // ⚠️ 不报错,类型 number,拼错也查不出

// ✅ 已知键用具体 interface,只有真动态才上索引签名

A string index signature makes every property access type-check, so typos like `d.conut` pass silently as the value type. Reserve index signatures for truly dynamic keys and list known keys explicitly.

What this tool does

Searchable TypeScript cheat sheet — 100+ snippets working TypeScript developers actually type, not toy "let x: number = 1" examples. Fourteen categories: basics (string / number / boolean / null / undefined / never / unknown / any / void / literal types / readonly arrays / tuples), interface vs type alias (extends, declaration merging, index + call signatures), union and intersection (A | B vs A & B, distributive over union, discriminated unions), generics (single + multi type parameter, extends constraint, default parameter, keyof constraint, generic React components, generic classes), utility types (Partial, Required, Readonly, Pick, Omit, Record, Exclude, Extract, NonNullable, ReturnType, Parameters, Awaited, ConstructorParameters, InstanceType, Uppercase / Lowercase / Capitalize), type narrowing (typeof, instanceof, in, equality, truthy, user-defined guard with `is`, assertion function with `asserts`, discriminated switch with never exhaustiveness check), mapped types (homomorphic, key remapping with `as`, + / - modifiers), conditional types (T extends U ? X : Y, distributive, wrap to prevent distribution, chain), infer (ReturnType, Promise unwrap, tuple head/tail, array element, template parsing), template literal types (4.1+, union cross product, recursive string parsing), assertions (as Foo, as const, satisfies 4.9+, non-null !), declaration merging (interface auto-merge, module augmentation, global), decorators (legacy experimental vs Stage 3 5.0+), and seven pitfalls (any vs unknown, non-null ! abuse, type vs interface choice, parameter variance, enum runtime cost, unsafe as cast, void return widening). Every entry: bilingual title, runnable code, bilingual description. Search across title / code / description together. 100% client-side, zero requests. Pair with Python / SQL / Vim / Regex cheatsheets and JSON Formatter for the full stack.

Tool details

Input
Text
The page exposes text boxes, numeric controls, file pickers, or structured inputs depending on the tool.
Output
Live result + Copy
The result area focuses on usable output, with copy, download, or preview actions when supported.
Privacy
May use a live lookup
A network call is detected in the component, so redact sensitive data when appropriate.
Save / share
No account required
Open the page and use it; whether results survive refresh depends on the tool.
Performance budget
Initial JS <= 32 KB
No WASM budget is declared, keeping the tool quick to open on mobile.
Best fit
Developer & DevOps · Developer
Category and role tags drive related tools, internal links, and quick fit checks.

How to use

  1. 1. Input

    Paste or drop your content into the tool panel.

  2. 2. Process

    Click the button. All processing is local in your browser.

  3. 3. Copy / Download

    Copy the result or download to disk in one click.

How TypeScript Cheatsheet fits into your work

Use it in the small gaps between coding, reviewing, debugging, and shipping.

Developer jobs

  • Formatting, validating, shrinking, or inspecting code-adjacent text.
  • Preparing snippets for documentation, tickets, commits, or handoff.
  • Checking a small payload quickly without switching tools.

Developer checks

  • Run irreversible transforms like minify or obfuscate on a copy.
  • Keep secrets out of pasted snippets unless the tool explicitly stays local.
  • Use your normal tests or linter before shipping transformed code.

Good next steps

These links move the current task into a more complete workflow.

  1. 1 JSON Formatter & Validator Format, validate, and minify JSON instantly — right in your browser. Open
  2. 2 Python Cheatsheet Python cheat sheet — 100+ idiomatic Python snippets for string, list, dict, file, async, with real examples. Open
  3. 3 SQL Cheatsheet SQL cheat sheet — 100+ statements covering SELECT, JOIN, window functions, indexing, MySQL/PostgreSQL/SQLite differences. Open

Real-world use cases

  • Pin down the exact utility type during a code review

    A reviewer flags a 400-line PR where someone hand-wrote a Partial-style mapped type instead of using the built-in. You filter to "Utility types", copy the Pick + Omit and Partial snippets, and leave three line comments swapping 25 lines of bespoke types for stdlib one-liners. The PR shrinks and the diff stays reviewable.

  • Write a discriminated-union state machine that compiles

    You are modeling a fetch hook with idle / loading / success / error states and keep accessing .data on the error branch. Filter to "Union and intersection", copy the discriminated union plus the never exhaustiveness switch, and the compiler now rejects any branch you forgot. One 12-line pattern kills a whole class of runtime undefined errors.

  • Extract a Promise's value type with infer at 2am

    A teammate's API returns Promise<User[]> and you need just User without importing it. You search "infer", copy the Awaited-with-infer conditional type, and write ElementOf<Awaited<ReturnType<typeof fetchUsers>>>. No new import, no any, and the type stays in sync if the endpoint shape changes later.

  • Validate a config object with satisfies, not as

    Your theme config has 40 color keys and you want the compiler to catch a typo while keeping each key's literal type for autocomplete. You copy the satisfies snippet, change `as const` to `satisfies Theme`, and now a misspelled "primry" fails the build while `theme.primary` still infers as the exact hex string downstream.

Common pitfalls

  • Reaching for `any` to silence an error when you mean `unknown`. `any` disables every check downstream; `unknown` forces a `typeof`/guard before use. Copy the unknown snippet and narrow instead.

  • Using `as Foo` to "fix" a type error. A cast is unchecked and just hides the mismatch until runtime. Prefer `satisfies` for validation, and only cast when you truly know more than the compiler (e.g. a known DOM element).

  • Picking `interface` for a union or computed shape and then fighting it. Interfaces cannot express unions, tuples, or conditional types. Use a `type` alias the moment you go past a flat object shape.

Privacy

Everything runs in your browser. The cheat sheet is a single static page and search filters an in-memory snippet array, so your search terms never leave the tab and nothing is written to the URL. No code execution, no upload, no network requests. Safe to use behind a corporate proxy or on an air-gapped machine.

FAQ

Tool combos

Folks in your role tend to reach for these alongside this tool.

Made by Toolora · 100% client-side · Updated 2026-06-13