Vibe Engines
YouTube

Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path

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 by erasableSyntaxOnly

Type operators

keyof T
union of the keys: keyof User is "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 const array: 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 readonly or optionality
as `get${Capitalize<K & string>}`
key remapping inside a mapped type: name becomes getName
`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]), not first<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"; Extract keeps the matching members
Awaited<T>
unwrap promises recursively: Awaited<Promise<Promise<number>>> is number
InstanceType<typeof C>
the instance type a class constructor produces
Uppercase / Capitalize
string-literal transforms; also Lowercase and Uncapitalize

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; any disables 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 x as 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
private is checked at compile time only; #field is enforced at runtime too
abstract class
cannot be instantiated; subclasses must implement its abstract members
readonly string[]
no push or splice — 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, noImplicitAny and the rest — the baseline for new code
noUncheckedIndexedAccess
arr[i] and record[key] become possibly undefined — catches missing keys
exactOptionalPropertyTypes
name?: string no longer accepts an explicit undefined
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.ts files 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.