TypeScript Types Quick Reference
The type-system features you actually use — unions, generics, utility types, narrowing, and the syntax that trips people up.
TypeScript checks JavaScript at compile time and then erases the types, so almost nothing on this sheet exists at runtime — enums and parameter properties are the notable exceptions. Types are structural: a value fits a type if it has the right shape, whatever it was declared as.
The model that unlocks the advanced rows: a type is a set of values. A union widens the set, an intersection narrows it, never is the empty set and unknown is everything. Narrowing is the compiler shrinking that set as your if checks run.
Building blocks
- Union / literal
type Dir = "n" | "s" | "e" | "w"- Interface vs type
- interface for object shapes (extendable); type for unions/aliases
- Optional / readonly
{ name?: string; readonly id: number }- Function type
type Fn = (a: number) => string- Array / tuple
number[]·[string, number]- A & B
- intersection: a value with every member of both types
- { [key: string]: number }
- index signature: any string key maps to a number, like
Record<string, number> - x satisfies T
- check a value against a type without widening it — exact keys and literals survive (4.9+)
- enum vs union
- prefer
"a" | "b": enums emit runtime code and are banned byerasableSyntaxOnly
Type operators
- keyof T
- union of the keys:
keyof Useris"id" | "name" | "tags" - typeof value
- in a type position, the type of a variable:
type Config = typeof config - T["id"] / T[number]
- indexed access: one property’s type / an array or tuple’s element type
- (typeof roles)[number]
- with an
as constarray: the union of its string values - { [K in keyof T]?: T[K] }
- mapped type: transform every property — this one is
Partial<T> - -readonly / -?
- mapped-type modifiers that strip
readonlyor optionality - as `get${Capitalize<K & string>}`
- key remapping inside a mapped type:
namebecomesgetName - `on${Capitalize<E>}`
- template literal type:
"click" | "focus"becomes"onClick" | "onFocus" - T extends U ? X : Y
- conditional type; over a union it distributes —
[T] extends [U]stops that - infer R
- capture part of a type in a conditional:
T extends Promise<infer R> ? R : T
Generics
- Generic function
function first<T>(xs: T[]): T { return xs[0]; }- Constraint
<T extends { id: number }>- Default
<T = string>- <K extends keyof T>
prop(obj, key): T[K]— only real keys allowed, and the result follows the key- Inference
- type arguments come from the call: write
first([1, 2]), notfirst<number>(…) - <const T>
- infer literal, readonly types from arguments, as if the caller wrote
as const(5.0+) - NoInfer<T>
- keep one parameter from widening
T; the other arguments decide it (5.4+)
Utility types
- Partial / Required
Partial<T>all optional ·Required<T>all required- Pick / Omit
Pick<T, "a" | "b">·Omit<T, "id">- Record
Record<string, number>— a keyed map- Return / Parameters
ReturnType<typeof f>·Parameters<typeof f>- Readonly / NonNullable
Readonly<T>·NonNullable<T>- Exclude / Extract
Exclude<"a" | "b", "a">is"b";Extractkeeps the matching members- Awaited<T>
- unwrap promises recursively:
Awaited<Promise<Promise<number>>>isnumber - InstanceType<typeof C>
- the instance type a class constructor produces
- Uppercase / Capitalize
- string-literal transforms; also
LowercaseandUncapitalize
Narrowing & escape hatches
- typeof / in / instanceof
if (typeof x === "string")narrows the type- Type guard
function isCat(a: Animal): a is Cat- Non-null assertion
el!.focus()— "trust me, not null" (use sparingly)- as const
const dirs = ["n","s"] as const— literal, readonly tuple- unknown vs any
- Prefer
unknown— forces a check before use;anydisables type-checking - switch (s.kind)
- discriminated union: a shared literal field narrows to the matching member in each case
- const x: never = s
- exhaustiveness check in
default— stops compiling when a member is unhandled - asserts x is string
- assertion function: after the call, TypeScript treats
xas narrowed - as unknown as T
- forces an assertion TypeScript rejects as unrelated — a red flag in review
- // @ts-expect-error
- suppress the next line’s error, and fail if there is none — safer than
@ts-ignore
Functions & classes
- Overloads
- several signatures above one implementation; callers only ever see the signatures
- (...rest: [number, boolean])
- rest parameters typed as a tuple — fixed count and order
- constructor(private x: number)
- parameter property: declares and assigns the field in one step
- private vs #private
privateis checked at compile time only;#fieldis enforced at runtime too- abstract class
- cannot be instantiated; subclasses must implement its
abstractmembers - readonly string[]
- no
pushorsplice— accept it in parameters you do not mutate - class C implements I
- checks the class against an interface; it adds no members or types by itself
tsconfig & modules
- "strict": true
- enables
strictNullChecks,noImplicitAnyand the rest — the baseline for new code - noUncheckedIndexedAccess
arr[i]andrecord[key]become possiblyundefined— catches missing keys- exactOptionalPropertyTypes
name?: stringno longer accepts an explicitundefined- import type { Foo }
- type-only import, erased from output — required for types under
verbatimModuleSyntax - erasableSyntaxOnly
- bans enums, namespaces and parameter properties so Node can strip types directly (5.8+)
- declare module "*.svg"
- type a non-TypeScript import;
.d.tsfiles describe code that exists elsewhere - tsc --noEmit
- type-check only — the usual CI step when a bundler does the compiling
Recipes
Model API states with a discriminated union
type Result<T> =
| { status: 'loading' }
| { status: 'error'; error: Error }
| { status: 'success'; data: T };
function render(r: Result<string[]>): string {
switch (r.status) {
case 'loading': return 'Loading…';
case 'error': return r.error.message; // narrowed to the error member
case 'success': return r.data.join(', '); // narrowed to the success member
default: {
const unreachable: never = r; // compile error if a status is added
return unreachable;
}
}
}Impossible states, such as data and error at once, cannot be expressed. Add a fourth status and every switch that lacks a case for it stops compiling.
Derive types from one source of truth
const ROLES = ['admin', 'editor', 'viewer'] as const;
type Role = (typeof ROLES)[number]; // "admin" | "editor" | "viewer"
const defaults = { theme: 'dark', pageSize: 20 };
type Settings = typeof defaults; // { theme: string; pageSize: number }
type SettingKey = keyof Settings; // "theme" | "pageSize"
function isRole(x: string): x is Role {
return (ROLES as readonly string[]).includes(x);
}The array is both the runtime list and the type, so they cannot drift apart. The cast in isRole is needed because includes on a readonly tuple of literals only accepts those literals.
Type a config object without losing its keys
type Route = { path: string; auth?: boolean };
const routes = {
home: { path: '/' },
admin: { path: '/admin', auth: true },
} satisfies Record<string, Route>;
routes.admin.path; // ok, with autocomplete
routes.contact; // error: property 'contact' does not exist
type RouteName = keyof typeof routes; // "home" | "admin"With an annotation instead (const routes: Record<string, Route>) the keys widen to string: typos compile and keyof gives string. satisfies checks the shape and keeps the literal keys.
A type-safe getter and a typed event map
function prop<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const id = prop({ id: 1, name: 'Ana' }, 'id'); // number
prop({ id: 1 }, 'nope'); // error: not a key
type Events = { click: { x: number; y: number }; key: { code: string } };
function on<E extends keyof Events>(event: E, handler: (payload: Events[E]) => void) {}
on('click', (p) => p.x); // p is { x: number; y: number }
on('key', (p) => p.code); // p is { code: string }The constraint K extends keyof T ties the key to the object, and the indexed access T[K] makes the return type follow whichever key the caller passes.
Turn on the strict flags that catch real bugs
{
"compilerOptions": {
"target": "es2022",
"module": "esnext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true
}
}noUncheckedIndexedAccess finds the most real bugs and is the noisiest to adopt, so enable flags one at a time in an existing codebase. Set module explicitly: with verbatimModuleSyntax on a CommonJS default, every ESM export is an error.
Questions about TypeScript Types Cheat Sheet
What is the difference between type and interface in TypeScript?
Both can describe the shape of an object, and for that job they are nearly interchangeable. An interface can be extended with extends and can be reopened: two declarations with the same name merge, which is how libraries let you add fields to their types. A type alias cannot merge, but it can name anything, including unions, tuples, mapped and conditional types, which interfaces cannot express. A common convention is interface for public object shapes and classes to implement, and type for unions and computed types. Pick one style per codebase and stay consistent.
What is the difference between any and unknown?
Both accept any value, but they treat what comes next very differently. any switches type checking off: you can call methods, read properties and pass the value anywhere, and mistakes surface only at runtime. unknown is the type-safe counterpart: you can store and pass the value, but you cannot use it until you narrow it with a check such as typeof, instanceof, a type guard or schema validation. Use unknown for data from outside the program, such as JSON.parse results, API responses and caught errors, and reserve any for rare escape hatches.
What does the satisfies operator do in TypeScript?
satisfies, added in TypeScript 4.9, checks that a value matches a type without changing the type TypeScript infers for it. Writing const config: Config = ... widens the variable to Config, so specific literal values and exact keys are forgotten. Writing const config = ... satisfies Config still reports any mistake against Config, but keeps the narrower inferred type, so keyof typeof config gives the real keys and literal values stay literal. It is most useful for configuration objects, route tables and lookup maps, where you want validation and precise autocomplete at the same time.
What is a discriminated union in TypeScript?
It is a union of object types that all share one property with a different literal value in each member, such as status set to loading, error or success. Checking that property, usually in a switch or an if, tells TypeScript exactly which member you have, so inside each branch it allows only the fields that member really has. Combined with an exhaustiveness check that assigns the leftover value to never, adding a new member makes every switch that forgot to handle it fail to compile. They are the standard way to model states, events and messages.