TypeScript

Complete TypeScript cheat sheet covering types, interfaces, generics, utility types, decorators, and common interview patterns.

11 sections23 cards

What it adds over JavaScript

TypeScript is a superset of JavaScript — all valid JS is valid TS. It adds a static type system that catches errors at compile time instead of runtime.

The compiler (tsc) strips all types and emits plain JS. Types exist only during development — zero runtime cost.

Key benefits: catch typos and wrong types before running code, better IDE autocomplete and refactoring, self-documenting code, safer large-scale refactors.

TS doesn't make JS safer at runtime — it only helps during development. Bad data from an API can still break things.

Primitives

string, number, boolean, null, undefined, symbol, bigint

let name: string = 'Alice'

let age: number = 25

let active: boolean = true

any — opt out of type checking. Avoid — defeats the purpose of TS.

unknown — like any but safer. Must type-check before using. Prefer over any.

never — a value that never occurs. Function that always throws or infinite loops returns never.

void — function returns nothing. Different from undefined — void means the return value won't be used.

Arrays & Tuples

string[] — array of strings. Equivalent: Array<string>.

number[][] — 2D array.

Tuple — fixed-length array with known types at each position:

let pair: [string, number] = ['Alice', 25]

Named tuple: [name: string, age: number] — labels for readability only.

Optional tuple element: [string, number?]

Rest in tuple: [string, ...number[]]

Type inference

TS infers types when you initialize a variable — you don't always need to annotate explicitly.

let name = 'Alice' → TS infers string.

let nums = [1, 2, 3] → TS infers number[].

Annotate when: function return types, function parameters, variables declared without initialization, and when inference is too wide.

Over-annotating is noise. Let inference work where it's obvious, annotate at boundaries (function signatures, exports).

Interface

Describes the shape of an object. Open — can be extended and merged.

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

? — optional property.

readonly id: number — cannot be reassigned after creation.

Index signature: { [key: string]: number } — object with any string keys, number values.

Declaration merging: declare the same interface twice and TS merges them. Useful for extending third-party types.

Extending: interface Admin extends User { role: string }

Type alias

type User = { name: string; age: number }

More flexible than interface — can alias primitives, unions, tuples, functions.

type ID = string | number

type Callback = (err: Error | null, data: string) => void

Interface vs type alias:

Use interface for object shapes that might be extended or merged. Use type for unions, intersections, mapped types, or when you need to alias a non-object type. In practice — pick one and be consistent. interface is preferred for public API shapes.

Union & Intersection

Union A | B — value is either A or B. Narrow with type guards before using type-specific properties.

type Result = Success | Error

Intersection A & B — value has all properties of both A and B. Combines types.

type AdminUser = User & { role: string }

Discriminated union — union of types that share a common literal field (kind, type). TS narrows based on that field.

type Shape = { kind: 'circle'; radius: number } | { kind: 'rect'; width: number; height: number }

Function types

Annotate params and return type:

function add(a: number, b: number): number { return a + b }

Arrow: const add = (a: number, b: number): number => a + b

Optional param: function greet(name?: string) — type becomes string | undefined.

Default param: function greet(name: string = 'World')

Rest: function sum(...nums: number[]): number

Function type alias: type Add = (a: number, b: number) => number

Overloads — multiple signatures for one function. Last signature is the implementation (not visible externally).

void vs never return

void — function completes but returns nothing useful. Caller shouldn't use the return value.

never — function never completes normally. Throws an error or loops forever.

function fail(msg: string): never { throw new Error(msg) }

TS uses never in exhaustiveness checks — if a switch is exhaustive, the default branch type is never. Assigning to a never variable in the default case catches missing cases at compile time.

Generic functions

Write functions that work with any type while preserving type safety.

function identity<T>(value: T): T { return value }

identity<string>('hello') or let TS infer: identity('hello')

Multiple type params: function pair<K, V>(key: K, val: V): [K, V]

Constrain with extends: function getLength<T extends { length: number }>(val: T): number — T must have a length property.

keyof constraint: function getProp<T, K extends keyof T>(obj: T, key: K): T[K] — type-safe property access.

Generic interfaces & classes

interface Box<T> { value: T; unwrap(): T }

class Stack<T> { private items: T[] = []; push(item: T) { this.items.push(item) } pop(): T | undefined { return this.items.pop() } }

Default type param: interface Response<T = unknown> { data: T; status: number }

Don't overuse generics. If a function only ever uses one type, just use that type directly. Generics add complexity — only reach for them when you genuinely need type-level reuse.

Object transformation

Partial<T> — makes all properties optional. Useful for update payloads.

Required<T> — makes all properties required.

Readonly<T> — makes all properties readonly.

Pick<T, 'a' | 'b'> — keep only specified keys.

Omit<T, 'password'> — remove specified keys.

Record<K, V> — object type with keys K and values V. Record<string, number>

Union transformation

Exclude<T, U> — remove from union T the types assignable to U.

Extract<T, U> — keep from union T only types assignable to U.

NonNullable<T> — remove null and undefined from T.

Function utilities:

ReturnType<typeof fn> — extract return type of a function.

Parameters<typeof fn> — extract parameter types as a tuple.

InstanceType<typeof MyClass> — extract instance type of a class.

Awaited<T> — unwrap Promise type. Awaited<Promise<string>>string.

Type guards

TS narrows the type inside conditional blocks.

typeof val === 'string' — primitive type guard.

val instanceof Date — class instance guard.

'name' in obj — property existence guard.

Custom type guard: function isUser(val: unknown): val is User { return typeof val === 'object' && val !== null && 'name' in val }

The val is User return type is a type predicate — tells TS the type is narrowed inside the if block.

Nullish handling

Non-null assertion: value! — tells TS the value is definitely not null/undefined. Use sparingly — you're lying to the compiler.

Optional chaining: user?.address?.city — short-circuits to undefined.

Nullish coalescing: value ?? 'default' — fallback for null/undefined only.

strictNullChecks — TS compiler option. When enabled, null and undefined are not assignable to other types unless explicitly included. Always enable this.

Satisfies operator

satisfies (TS 4.9) — validates that a value matches a type without widening the inferred type.

const palette = { red: [255, 0, 0], blue: '#0000ff' } satisfies Record<string, string | number[]>

Unlike a type annotation, satisfies keeps the narrower inferred type — so palette.red is still number[], not string | number[].

Use when you want type validation but also want to keep the precise literal/inferred type for downstream use.

Class features

Access modifiers: public (default), private, protected.

Parameter shorthand: constructor(private name: string, public age: number) — declares and assigns in one step.

readonly — can only be set in constructor.

Abstract class: abstract class Animal — cannot be instantiated. Can have abstract methods that subclasses must implement.

Implements: class Dog implements Animal — class must satisfy the interface. Purely a compile-time check.

Private fields: #field — JS native private (truly private at runtime). vs private keyword which is only a TS compile-time restriction.

Mapped types

Transform all properties of a type:

type Optional<T> = { [K in keyof T]?: T[K] } — same as Partial.

type Stringify<T> = { [K in keyof T]: string } — all values become string.

Modifiers: +? add optional, -? remove optional, -readonly remove readonly.

Key remapping with as: { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] }

Conditional types

T extends U ? X : Y — if T is assignable to U, result is X, else Y.

type IsString<T> = T extends string ? true : false

infer — extract a type inside a conditional: type UnpackPromise<T> = T extends Promise<infer U> ? U : T

Distributive: conditional types distribute over union types automatically. IsString<string | number>true | false.

Template literal types

Build string types with template syntax:

type EventName = `on${Capitalize<string>}` — matches 'onClick', 'onFocus', etc.

type CSSProperty = `${'margin' | 'padding'}-${'top' | 'bottom'}` — generates all combinations.

Useful for: typed event names, CSS property keys, API endpoint strings, i18n keys.

Important compiler options

strict — enables all strict checks. Always turn this on. Includes: strictNullChecks, noImplicitAny, strictFunctionTypes, and more.

target — JS version to emit. ES2020 or later for modern Node/browsers.

module — module system. ESNext + moduleResolution: bundler for modern setups.

outDir — where compiled JS goes.

rootDir — root of source files.

paths — path aliases. { \"@/*\": [\"./src/*\"] }

lib — built-in type definitions to include. [\"ES2020\", \"DOM\"] for browser projects.

noUnusedLocals / noUnusedParameters — error on unused variables.

noUncheckedIndexedAccess — array access returns T | undefined. Stricter but catches real bugs.

Common traps

any vs unknown — both accept any value, but unknown forces you to type-check before use. Use unknown for truly unknown input (API responses, user input).

interface merging vs type — only interfaces can be merged across declarations. Types cannot.

Structural typing — TS uses duck typing. If an object has all required properties, it's assignable even if it wasn't explicitly declared as that type.

Type assertion as — doesn't convert, just tells TS to treat a value as a type. Can be wrong. Prefer type guards over assertions.

Enums compile to real JS objects (numeric) or string maps. const enum is inlined at compile time — no runtime object. union of literals is often a cleaner alternative to enums.

Things to know cold

Declaration files .d.ts — type-only files. No runtime code. Used by libraries to expose types. @types/package packages are community-maintained declaration files.

as const — makes a value deeply readonly and narrows literals. ['a', 'b'] as const → type is readonly ['a', 'b'] not string[].

Type vs runtime — types are erased. You cannot use a TS type at runtime (typeof MyInterface doesn't work). Use zod or similar for runtime validation.

Excess property checking — only happens on object literals assigned directly. Assigning via a variable bypasses it.