TypeScript Cheatsheet
Complete reference — types, generics, utility types, patterns, async, decorators, and interview-ready snippets.
Primitives
Objects & Interfaces
Unions & Narrowing
Generics
Utility Types
Functions
Classes
Async & Promises
Advanced Types
Common Patterns
Gotchas
Primitive & Built-in Types
All Primitive Types + Type Assertions + Literals
Basics
// ─── Primitive Types ───────────────────────────────────────
let name: string = "Alice";
let age: number = 30; // integer AND float — no separate int type
let active: boolean = true;
let id: bigint = 9007199254740991n;
let sym: symbol = Symbol("key");
let nothing: null = null;
let undef: undefined = undefined;
// ─── Special Types ─────────────────────────────────────────
let anything: any; // ⚠ disables type checking — avoid
let safe: unknown; // must narrow before use — prefer over any
function fail(): never { // function that never returns
throw new Error("boom");
}
function log(): void {} // returns nothing
// ─── Literal Types ─────────────────────────────────────────
type Direction = "north" | "south" | "east" | "west";
type Status = 200 | 400 | 404 | 500;
type Enabled = true; // literal boolean
// ─── Type Assertions ───────────────────────────────────────
const el = document.getElementById("btn") as HTMLButtonElement;
const len = (anything as string).length;
const num = <number>anything; // JSX-incompatible syntax, prefer `as`
const sure = anything!; // non-null assertion — asserts not null/undefined
// ─── const assertion ───────────────────────────────────────
const config = { host: "localhost", port: 3000 } as const;
// config.host is "localhost" (literal), not string — fully readonly
// ─── Arrays & Tuples ───────────────────────────────────────
const nums: number[] = [1, 2, 3];
const strs: Array<string> = ["a", "b"];
const pair: [string, number] = ["age", 30]; // tuple — exact length+types
const triple: [string, number, boolean?] = ["x", 1]; // optional 3rd
Objects, Interfaces & Type Aliases
interface vs type — Differences + Extending + Index Signatures
Core
// ─── interface (preferred for object shapes & public API) ──
interface User {
readonly id: number; // readonly — cannot reassign after init
name: string;
email?: string; // optional property
greet(): string; // method signature
}
// Extending interfaces (open for extension — key difference from type)
interface Admin extends User {
role: "super" | "moderator";
}
// Interface merging (declaration merging — unique to interface)
interface Window { myLib: string } // extends existing Window global
// ─── type alias (preferred for unions, primitives, mapped types) ──
type ID = string | number;
type Point = { x: number; y: number };
type AdminUser = User & { role: string }; // intersection
// ─── Index Signatures ──────────────────────────────────────
interface StringMap {
[key: string]: string; // any string key → string value
}
type Record2<K extends string, V> = { [P in K]: V };
// ─── Nested + Optional chaining ───────────────────────────
interface Config {
db?: { host: string; port?: number };
}
const port = config.db?.port ?? 5432; // optional chain + nullish coalescing
// ─── Satisfies operator (TS 4.9+) ─────────────────────────
const palette = {
red: [255, 0, 0],
green: "#00ff00",
} satisfies Record<string, string | number[]>;
// palette.red is still number[] (not string | number[]) — narrowed!
Unions, Intersections & Type Narrowing
Discriminated Unions + All Narrowing Techniques
Critical
// ─── typeof narrowing ──────────────────────────────────────
function format(val: string | number) {
if (typeof val === "string") return val.toUpperCase(); // val: string here
return val.toFixed(2); // val: number here
}
// ─── instanceof narrowing ──────────────────────────────────
function handleError(e: unknown) {
if (e instanceof Error) return e.message; // e: Error
return String(e);
}
// ─── in narrowing ──────────────────────────────────────────
type Fish = { swim(): void };
type Bird = { fly(): void };
function move(animal: Fish | Bird) {
if ("swim" in animal) animal.swim(); // animal: Fish
else animal.fly(); // animal: Bird
}
// ─── Discriminated Union (most powerful pattern) ───────────
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rectangle"; width: number; height: number }
| { kind: "triangle"; base: number; height: number };
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.radius ** 2;
case "rectangle": return s.width * s.height;
case "triangle": return 0.5 * s.base * s.height;
default:
const _exhaustive: never = s; // compile error if case missing!
return _exhaustive;
}
}
// ─── Type Guard (user-defined) ─────────────────────────────
function isFish(a: Fish | Bird): a is Fish {
return (a as Fish).swim !== undefined;
}
// ─── Assertion Functions (TS 3.7+) ─────────────────────────
function assert(cond: unknown, msg: string): asserts cond {
if (!cond) throw new Error(msg);
}
function assertString(v: unknown): asserts v is string {
if (typeof v !== "string") throw new TypeError();
}
Generics
Generics — Functions, Interfaces, Constraints, Defaults, Conditional
Critical
// ─── Generic function ──────────────────────────────────────
function identity<T>(arg: T): T { return arg; }
identity<string>("hello"); // explicit
identity(42); // inferred T=number
// ─── Generic interface ─────────────────────────────────────
interface ApiResponse<T> {
data: T;
status: number;
error?: string;
}
type UserResponse = ApiResponse<User>;
// ─── Constraints (extends) ─────────────────────────────────
function getLength<T extends { length: number }>(arg: T): number {
return arg.length;
}
getLength("hello"); // ✓ string has length
getLength([1, 2, 3]); // ✓ array has length
// ─── keyof constraint ──────────────────────────────────────
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
getProperty({ a: 1, b: "two" }, "a"); // returns number
// ─── Default type params (TS 2.3+) ─────────────────────────
interface Container<T = string> { value: T }
const c: Container = { value: "hello" }; // T defaults to string
// ─── Conditional Types ─────────────────────────────────────
type IsArray<T> = T extends any[] ? "yes" : "no";
type A = IsArray<number[]>; // "yes"
type B = IsArray<string>; // "no"
// Infer within conditional
type UnwrapArray<T> = T extends (infer U)[] ? U : T;
type Num = UnwrapArray<number[]>; // number
type Str = UnwrapArray<string>; // string (not array, so T returned)
// ReturnType built with infer
type MyReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : never;
Utility Types
All Built-in Utility Types with Examples
Must Know
interface User { id: number; name: string; email: string; age?: number }
// ─── Object Transformation ────────────────────────────────
type PartialUser = Partial<User>; // all props optional
type RequiredUser = Required<User>; // all props required (removes ?)
type ReadonlyUser = Readonly<User>; // all props readonly
type UserRecord = Record<string, User>; // dictionary
// Pick & Omit — most used in practice
type UserPreview = Pick<User, "id" | "name">; // only id & name
type CreateUser = Omit<User, "id">; // everything except id
type UpdateUser = Partial<Omit<User, "id">>; // common patch body
// ─── Set operations on Union types ────────────────────────
type AB = "a" | "b" | "c";
type Exl = Exclude<AB, "a">; // "b" | "c" — removes from union
type Ext = Extract<AB, "a" | "d">; // "a" — keeps intersection
type NN = NonNullable<string | null | undefined>; // string
// ─── Function types ───────────────────────────────────────
type Fn = (x: number) => string;
type RT = ReturnType<Fn>; // string
type PT = Parameters<Fn>; // [x: number]
type CT = ConstructorParameters<typeof Date>; // Date constructor args
type IT = InstanceType<typeof Date>; // Date instance type
// ─── Awaited (TS 4.5+) ────────────────────────────────────
type Resolved = Awaited<Promise<Promise<string>>>; // string (deeply unwraps)
// ─── Template Literal Types (TS 4.1+) ────────────────────
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
type CRUD = "create" | "read" | "update" | "delete";
type APIRoute = `/api/${CRUD}`; // "/api/create" | "/api/read" | ...
Functions
Function Overloads, Rest, Callbacks, Higher-Order, this parameter
Functions
// ─── Basic signatures ──────────────────────────────────────
function add(a: number, b: number): number { return a + b; }
const mul = (a: number, b: number): number => a * b;
// Optional + default + rest
function greet(name: string, greeting = "Hello", ...titles: string[]): string {
return `${greeting} ${titles.join(" ")} ${name}`;
}
// ─── Function Overloads ────────────────────────────────────
function parse(input: string): number;
function parse(input: number): string;
function parse(input: string | number): string | number { // implementation
return typeof input === "string" ? Number(input) : String(input);
}
// ─── Callback types ───────────────────────────────────────
type Predicate<T> = (item: T) => boolean;
type Mapper<T, U> = (item: T, index: number) => U;
function filter<T>(arr: T[], pred: Predicate<T>): T[] {
return arr.filter(pred);
}
// ─── this parameter (fake param for type of `this`) ────────
interface Counter { count: number; increment(this: Counter): void }
const counter: Counter = {
count: 0,
increment() { this.count++; } // `this` is typed as Counter
};
// ─── Higher-order / Compose ────────────────────────────────
const compose = <A, B, C>(f: (b: B) => C, g: (a: A) => B) => (a: A) => f(g(a));
const doubleStr = compose((n: number) => n.toString(), (n: number) => n * 2);
Classes
Classes — Access Modifiers, Abstract, Mixins, Decorators
OOP
abstract class Animal {
// Access modifiers: public (default) | private | protected | readonly
constructor(
public readonly name: string, // shorthand property declaration
protected age: number,
private _secret: string = "hidden"
) {}
abstract speak(): string; // must implement in subclass
describe(): string {
return `${this.name} is ${this.age}`;
}
static create(name: string): this { // polymorphic this in factory
return new (this as any)(name, 0);
}
}
class Dog extends Animal {
speak(): string { return `${this.name} says Woof`; }
// Override: can access this.age (protected) but not this._secret (private)
}
// ─── Implements interface ──────────────────────────────────
interface Serializable { serialize(): string }
class Cat extends Animal implements Serializable {
speak() { return "Meow"; }
serialize() { return JSON.stringify({ name: this.name }); }
}
// ─── Private fields (runtime private — ES2022) ─────────────
class BankAccount {
#balance = 0; // truly private — not on prototype
deposit(n: number) { this.#balance += n; }
get balance() { return this.#balance; }
}
// ─── Mixin pattern ─────────────────────────────────────────
type Constructor<T = {}> = new (...args: any[]) => T;
function Timestamped<TBase extends Constructor>(Base: TBase) {
return class extends Base {
createdAt = new Date();
};
}
class TimestampedUser extends Timestamped(Animal) {
speak() { return "..."; }
}
Async / Promises / Error Handling
Async/Await, Promise combinators, Error handling patterns
Critical
// ─── Basic async/await ─────────────────────────────────────
async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json() as Promise<User>;
}
// ─── Promise combinators ───────────────────────────────────
// all — all succeed or reject fast on first failure
const [u1, u2] = await Promise.all([fetchUser(1), fetchUser(2)]);
// allSettled — wait for all, get status for each (never rejects)
const results = await Promise.allSettled([fetchUser(1), fetchUser(99)]);
results.forEach(r => {
if (r.status === "fulfilled") console.log(r.value);
else console.error(r.reason);
});
// race — first settled wins
const fastest = await Promise.race([fetchUser(1), fetchUser(2)]);
// any — first FULFILLED wins (ignores rejections; rejects only if ALL fail)
const first = await Promise.any([fetchUser(1), fetchUser(2)]);
// ─── Result type pattern (safer than try/catch everywhere) ─
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
async function safeGet<T>(fn: () => Promise<T>): Promise<Result<T>> {
try { return { ok: true, value: await fn() }; }
catch (e) { return { ok: false, error: e instanceof Error ? e : new Error(String(e)) }; }
}
// ─── Async iterator / generator ────────────────────────────
async function* paginate(url: string) {
let page = 1;
while (true) {
const data = await fetch(`${url}?page=${page++}`).then(r => r.json());
if (!data.length) break;
yield data;
}
}
for await (const page of paginate("/api/items")) { console.log(page); }
Advanced Types
Mapped Types, Template Literals, Decorators, Module Augmentation
Advanced
// ─── Mapped Types ──────────────────────────────────────────
type Nullable<T> = { [P in keyof T]: T[P] | null };
type Getters<T> = {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P]
};
// Getters<{name:string}> → { getName: () => string }
// ─── Recursive types ───────────────────────────────────────
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
// ─── Decorators (TS + experimentalDecorators) ──────────────
function log(target: any, key: string, desc: PropertyDescriptor) {
const original = desc.value;
desc.value = function(...args: any[]) {
console.log(`Calling ${key} with`, args);
return original.apply(this, args);
};
}
class Service {
@log
fetchData(id: number) { /* ... */ }
}
// ─── Branded / Opaque types ────────────────────────────────
type UserId = string & { readonly _brand: "UserId" };
type OrderId = string & { readonly _brand: "OrderId" };
const makeUserId = (id: string): UserId => id as UserId;
// Prevents accidentally passing UserId where OrderId expected
Enums — const enum, union alternative, reverse mapping
Enums
// Numeric enum (default — auto-increments)
enum Direction { Up, Down, Left, Right } // 0,1,2,3
Direction.Up // 0
Direction[0] // "Up" — reverse mapping (numeric only!)
// String enum (most common — no reverse mapping, clear values)
enum Status { Active = "ACTIVE", Inactive = "INACTIVE" }
// const enum — inlined at compile time (zero runtime overhead)
const enum Color { Red, Green, Blue }
let c = Color.Red; // compiled to: let c = 0;
// ⚠ Modern preference: union of string literals (simpler, no runtime object)
type StatusLiteral = "ACTIVE" | "INACTIVE";
// Avoids enum pitfalls (numeric assignability, bloated JS output)
Common TypeScript Patterns
Builder, Repository, Event Emitter, Dependency Injection in TypeScript
Patterns
// ─── Builder Pattern ───────────────────────────────────────
class QueryBuilder {
private parts: string[] = [];
select(...cols: string[]): this {
this.parts.push(`SELECT ${cols.join(", ")}`); return this;
}
from(table: string): this {
this.parts.push(`FROM ${table}`); return this;
}
where(cond: string): this {
this.parts.push(`WHERE ${cond}`); return this;
}
build(): string { return this.parts.join(" "); }
}
const q = new QueryBuilder().select("id","name").from("users").where("active=1").build();
// ─── Generic Repository ────────────────────────────────────
interface Repository<T, ID> {
findById(id: ID): Promise<T | null>;
findAll(): Promise<T[]>;
save(entity: T): Promise<T>;
delete(id: ID): Promise<void>;
}
class UserRepository implements Repository<User, number> { /* ... */ }
// ─── Typed Event Emitter ───────────────────────────────────
type Events = {
login: [userId: string];
logout: [userId: string, reason: string];
error: [err: Error];
};
class TypedEmitter<E extends Record<string, any[]>> {
private listeners = new Map<string, Function[]>();
on<K extends keyof E>(event: K, cb: (...args: E[K]) => void) {
(this.listeners.get(event as string) ?? this.listeners.set(event as string, []).get(event as string)!).push(cb);
}
emit<K extends keyof E>(event: K, ...args: E[K]) {
this.listeners.get(event as string)?.forEach(cb => cb(...args));
}
}
Common Gotchas & Interview Tips
Top TypeScript Gotchas Every Developer Must Know
Interview
Structural vs Nominal Typing
interface Cat { name: string }
interface Dog { name: string }
const dog: Dog = { name: "Rex" };
const cat: Cat = dog; // ✓ structural — shapes match
Excess Property Checks
const u: User = { id:1, name:"A", extra:"x" }; // ✗ Error
const obj = { id:1, name:"A", extra:"x" };
const u2: User = obj; // ✓ via variable (no excess check)
Type vs typeof
const str = "hello";
type T1 = typeof str; // "hello" (literal)
type T2 = ReturnType<typeof fetch>; // runtime value → type
Widening / Narrowing
let x = "hello"; // widened to string
const y = "hello"; // narrowed to "hello" literal
let z = null; // widened to null (not never)
Top Interview Points
- interface vs type: Use
interfacefor object shapes (extensible via declaration merging). Usetypefor unions, intersections, primitives, mapped types. - any vs unknown:
unknownrequires narrowing before use — much safer. Never useanyin production code. - Discriminated union + exhaustive check with
neveris the best pattern for state machines. - const enum inlines values (no runtime overhead) but breaks with isolatedModules — avoid in libraries.
- Type assertions (
as) bypass type checking — use sparingly. Prefer type guards or generic constraints. - strictNullChecks: Always on in production — forces explicit handling of null/undefined.
- Branded types prevent semantic errors (passing userId where productId expected).