cheat sheet

TypeScript Cheat Sheet

Every type, every pattern, searchable and filterable by difficulty.

/ to focus
difficulty:
Primitive Types
allbasic
string, number, boolean
let name: string = "Alice"; let age: number = 30; let active: boolean = true;
null, undefined, void, never, unknown
let n: null = null; let u: undefined = undefined; let y: unknown = 42; // safer than any function log(): void { console.log("hi"); } function fail(): never { throw new Error("!"); }
as const & satisfies
TS 4.9+intermediate
as const - narrow to literal types
const config = { host: "localhost", port: 3000 } as const; // port → 3000 (literal), not number const dirs = ["up", "down"] as const; type Dir = typeof dirs[number]; // "up" | "down"
satisfies - validate shape, keep inferred types
type Colors = Record<string, string | number[]>; const palette = { red: [255, 0, 0], green: "#00ff00", } satisfies Colors; palette.red.map(x => x); // still number[] palette.green.toUpperCase(); // still string
Union, Intersection & Discriminated Unions
allintermediate
Union and intersection
let id: string | number; type Status = "active" | "inactive" | "pending"; type Admin = User & Permissions; // all props of both
Discriminated union with exhaustiveness check
type Shape = | { kind: "circle"; radius: number } | { kind: "rect"; width: number; height: 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.width * s.height; case "triangle": return 0.5 * s.base * s.h; } }
Type Guards
allintermediate
typeof and instanceof narrowing
function pad(x: string | number) { if (typeof x === "string") return x.toUpperCase(); return x * 2; } if (err instanceof Error) console.log(err.message);
User-defined type guard (is keyword)
function isUser(x: unknown): x is User { return typeof x === "object" && x !== null && "id" in x; } function assert(cond: boolean, msg: string): asserts cond { if (!cond) throw new Error(msg); }
Type Aliases & Enums
allbasic
Type alias
type Point = { x: number; y: number }; type ID = string | number;
String enum and const enum
enum Color { Red = "RED", Green = "GREEN", Blue = "BLUE" } const enum Dir { Up, Down, Left, Right } // inlined at compile time
interface vs type: quick reference
allintermediate
Featureinterfacetype
Object shapeyesyes
Union / intersectionnoyes
Primitive aliasnoyes
Tuplenoyes
Mapped / conditional typesnoyes
extends keywordyesvia &
Declaration mergingyesno
implements in classyesyes
Error messagescleanersometimes verbose
Interface Basics
allbasic
Define an interface
interface User { id: number; name: string; email?: string; // optional readonly createdAt: Date; // immutable }
Method signatures and index signature
interface Repo { find(id: number): User | undefined; save(user: User): void; [key: string]: unknown; }
Extending & Merging
allintermediate
Extend single and multiple interfaces
interface Dog extends Animal { breed: string } interface AdminUser extends User, Permissions { role: "admin" }
Declaration merging (interfaces only)
interface Window { myLib: MyLibrary } // In another file: interface Window { analytics: Analytics } // Both merge automatically. type aliases cannot do this.
Function Types
allbasic
Named, arrow, optional, default, rest
function add(a: number, b: number): number { return a + b; } const mul = (x: number, y: number): number => x * y; function greet(name: string, prefix = "Hi", title?: string) { return `${prefix}, ${title ?? ""}${name}`; } function sum(...nums: number[]): number { return nums.reduce((a, b) => a + b, 0); } type Handler = (event: MouseEvent) => void;
Async Functions
allintermediate
Promise<T>, await, typed catch
async function fetchUser(id: number): Promise<User> { const res = await fetch(`/users/${id}`); return res.json(); } async function load(): Promise<User | null> { try { return await fetchUser(1); } catch (err: unknown) { if (err instanceof Error) console.error(err.message); return null; } } const [user, posts] = await Promise.all([fetchUser(1), fetchPosts(1)]);
Overloads & this
alladvanced
Function overloads
function format(x: string): string; function format(x: number): string; function format(x: any): string { return String(x); }
this parameter (compile-time annotation only)
function onClick(this: HTMLButtonElement) { console.log(this.textContent); }
Class Basics
allbasic
Constructor shorthand, getters, static
class Person { static readonly species = "Homo sapiens"; constructor( public name: string, private _age: number, protected role: string = "user", ) {} get age() { return this._age; } set age(v: number) { if (v < 0) throw new Error("negative"); this._age = v; } }
Inheritance, Abstract & Implements
allintermediate
Abstract class and implements
abstract class Shape { abstract area(): number; describe() { return `area: ${this.area()}`; } } class Square extends Shape { constructor(private s: number) { super(); } area() { return this.s ** 2; } } interface Serializable { serialize(): string } class Model implements Serializable { serialize() { return JSON.stringify(this); } }
Generic Functions & Interfaces
allintermediate
Generic function, interface, constraint, keyof
function identity<T>(value: T): T { return value; } interface ApiResponse<T> { data: T; status: number } function getLength<T extends { length: number }>(x: T) { return x.length; } function getProp<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; }
Generic class and default type parameter
class Stack<T> { private items: T[] = []; push(item: T) { this.items.push(item); } pop(): T | undefined { return this.items.pop(); } } interface Container<T = string> { value: T }
Object Utility Types
allutility
Partial, Required, Readonly
type A = Partial<User>; // all props optional type B = Required<User>; // removes all ? type C = Readonly<User>; // all props readonly
Pick vs Omit
// Pick: allowlist - only named keys survive type Preview = Pick<User, "id" | "name">; // Omit: blocklist - all keys except named ones type Public = Omit<User, "password">;
Record, Exclude, Extract, NonNullable
type Scores = Record<string, number>; type T1 = Exclude<"a"|"b"|"c", "b">; // "a"|"c" type T2 = Extract<"a"|"b", "b"|"c">; // "b" type T3 = NonNullable<string|null>; // string
ReturnType, Parameters, Awaited
type R = ReturnType<typeof fetchUser>; type P = Parameters<typeof fetchUser>; type AW = Awaited<Promise<string>>; // string
Mapped Types
alladvanced
Basic mapped type with modifiers
type Optional<T> = { [K in keyof T]?: T[K] }; type Mutable<T> = { -readonly [K in keyof T]: T[K] };
Remapping keys with as
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] };
Conditional Types & infer
alladvanced
Basic conditional type
type IsArray<T> = T extends any[] ? "yes" : "no"; type A = IsArray<number[]>; // "yes" type B = IsArray<string>; // "no"
infer -- unwrap Promise type
type Unpack<T> = T extends Promise<infer U> ? U : T; type R = Unpack<Promise<string>>; // string
infer with extends constraint -- extract array element type
type ElementType<T> = T extends (infer E)[] ? E : never; type N = ElementType<number[]>; // number type S = ElementType<string[][]>; // string[] // With an upper bound constraint: type NumericEl<T> = T extends (infer E extends number)[] ? E : never; type X = NumericEl<1[] | 2[]>; // 1 | 2
Template Literal Types & Namespaces
TS 4+advanced
Build string literal unions
type Ev = "click" | "focus" | "blur"; type Handler = `on${Capitalize<Ev>}`; // "onClick" | "onFocus" | "onBlur" type U = Uppercase<"hello">; // "HELLO"
Namespace
namespace Validation { export interface Validator { isValid(s: string): boolean } export class Email implements Validator { isValid(s: string) { return s.includes("@"); } } }
Branded / Opaque Types
alladvanced
Branded type -- prevent mixing identical primitives
type Brand<T, B> = T & { readonly _brand: B }; type UserId = Brand<string, "UserId">; type OrderId = Brand<string, "OrderId">; function makeUserId(id: string): UserId { return id as UserId; } function getUser(id: UserId) { /* ... */ } const uid = makeUserId("u-123"); const oid = "o-456" as OrderId; getUser(uid); // ok // getUser(oid); // Error: OrderId is not assignable to UserId
Builder Pattern
alladvanced
Fluent builder with method chaining
class QueryBuilder { private conditions: string[] = []; private _limit = 100; private _table = ""; from(table: string): this { this._table = table; return this; } where(cond: string): this { this.conditions.push(cond); return this; } limit(n: number): this { this._limit = n; return this; } build(): string { const where = this.conditions.join(" AND "); return `SELECT * FROM ${this._table} WHERE ${where} LIMIT ${this._limit}`; } } const sql = new QueryBuilder() .from("users") .where("active = true") .limit(10) .build();
Type-safe builder using generics (step builder)
type BuilderState = { name?: string; age?: number }; function createUser() { const state: BuilderState = {}; return { name(n: string) { state.name = n; return this; }, age(a: number) { state.age = a; return this; }, build() { if (!state.name) throw new Error("name required"); return state as Required<BuilderState>; }, }; } const user = createUser().name("Alice").age(30).build();
noUncheckedIndexedAccess in Practice
allintermediate
What changes with noUncheckedIndexedAccess: true
// tsconfig: "noUncheckedIndexedAccess": true const arr: number[] = [1, 2, 3]; // WITHOUT the flag: const a = arr[0]; // type: number // WITH the flag: const b = arr[0]; // type: number | undefined // b.toFixed(2); // Error: b might be undefined // Fix 1: optional chaining b?.toFixed(2); // Fix 2: explicit check if (b !== undefined) b.toFixed(2); // Same applies to Record index: const map: Record<string, number> = {}; const c = map["key"]; // number | undefined
Class, Method & Property Decorators
TS 5+advanced
Class decorator
function Sealed(target: Function) { Object.seal(target); Object.seal(target.prototype); } @Sealed class BugReport { title = "report"; }
Method decorator
function Log(target: any, key: string, desc: PropertyDescriptor) { const orig = desc.value; desc.value = function(...args: any[]) { console.log(`${key} called with`, args); return orig.apply(this, args); }; } class Service { @Log getData(id: number) { /* ... */ } }
Props Typing
allReactbasic
Function component with typed props
import { FC } from "react"; type ButtonProps = { label: string; onClick: () => void; variant?: "primary" | "ghost"; disabled?: boolean; }; const Button: FC<ButtonProps> = ({ label, onClick, variant = "primary", disabled = false }) => ( <button onClick={onClick} disabled={disabled} className={variant}> {label} </button> );
Props with children
import { ReactNode } from "react"; type CardProps = { title: string; children: ReactNode; }; function Card({ title, children }: CardProps) { return <div><h2>{title}</h2>{children}</div>; }
Extending native HTML element props
import { ButtonHTMLAttributes } from "react"; type MyButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & { loading?: boolean; }; function MyButton({ loading, children, ...rest }: MyButtonProps) { return <button {...rest}>{loading ? "..." : children}</button>; }
Hooks Typing
allReactintermediate
useState with explicit type
import { useState } from "react"; const [count, setCount] = useState<number>(0); const [user, setUser] = useState<User | null>(null); const [items, setItems] = useState<string[]>([]);
useRef -- DOM element and mutable value
import { useRef } from "react"; // DOM ref: pass null as initial value const inputRef = useRef<HTMLInputElement>(null); inputRef.current?.focus(); // Mutable ref: no null initial value const timerRef = useRef<ReturnType<typeof setTimeout>>(undefined); timerRef.current = setTimeout(() => {}, 1000);
useReducer with typed action
type Action = | { type: "increment" } | { type: "decrement" } | { type: "reset"; payload: number }; function reducer(state: number, action: Action): number { switch (action.type) { case "increment": return state + 1; case "decrement": return state - 1; case "reset": return action.payload; } } const [count, dispatch] = useReducer(reducer, 0);
Event Types
allReactintermediate
Common React event types
import { ChangeEvent, MouseEvent, FormEvent, KeyboardEvent, FocusEvent, } from "react"; const handleChange = (e: ChangeEvent<HTMLInputElement>) => { console.log(e.target.value); }; const handleClick = (e: MouseEvent<HTMLButtonElement>) => { e.preventDefault(); }; const handleSubmit = (e: FormEvent<HTMLFormElement>) => { e.preventDefault(); };
Generic component with typed props
type ListProps<T> = { items: T[]; renderItem: (item: T) => React.ReactNode; keyExtractor: (item: T) => string; }; function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) { return ( <ul> {items.map(item => ( <li key={keyExtractor(item)}>{renderItem(item)}</li> ))} </ul> ); }
Imports & Exports
allbasic
Named, default, namespace imports
import { PI, square } from "./math"; import User from "./user"; // default import * as Utils from "./utils"; // namespace
Type-only import (TS 3.8+)
import type { User, Post } from "./types"; // Fully erased at runtime
Ambient module and global augmentation
// global.d.ts declare module "*.svg" { const src: string; export default src; } declare global { interface Window { myApp: AppInstance } }
tsconfig.json
allconfig file
Recommended strict base (tsconfig.json, not TypeScript source)
{ "compilerOptions": { "target": "ES2022", "module": "NodeNext", "strict": true, "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true, "outDir": "./dist", "sourceMap": true, "declaration": true } }
Path aliases (tsconfig.json)
{ "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"], "@components/*": ["src/components/*"] } } }

TypeScript cheat sheet for developers who want fast, reliable reference without digging through documentation.

Covers everything from primitives and type aliases to advanced patterns like mapped types, conditional types, branded types, and the builder pattern. Every snippet is syntax-highlighted, copy-ready, and tagged by difficulty so beginners and experienced engineers can both find what they need quickly.

What's inside:

  • Types & Basics - primitives, as const, satisfies, discriminated unions, type guards

  • Interfaces - definition, extending, declaration merging, plus a full interface vs type comparison table

  • Functions - typed functions, async/await, overloads, this parameter

  • Classes - constructor shorthand, abstract classes, getters/setters, implements

  • Generics - constraints, keyof, generic classes, default type parameters

  • Utility Types - Partial, Pick, Omit, Record, ReturnType, Awaited, and more

  • Advanced - mapped types, conditional types, infer with constraints, template literal types, namespaces

  • Patterns - branded/opaque types, builder pattern, noUncheckedIndexedAccess explained with real before/after examples

  • Decorators - class, method, and property decorators for TS 5+

  • React + TypeScript - props typing, FC, ReactNode, useRef, useReducer, all common event types, generic components

  • Modules & Config - imports, import type, ambient modules, recommended tsconfig.json options

Built-in difficulty filter (Basic / Intermediate / Advanced) lets you hide noise or focus on what you're learning. Search across all tabs instantly. Preview boxes explain what each type resolves to, not just what the syntax looks like.

Compatible with TypeScript 5.x, with version badges marking features introduced in TS 4+ and TS 5+.

What is TypeScript

TypeScript is a statically typed superset of JavaScript, developed and maintained by Microsoft.

It compiles down to plain JavaScript, which means it runs anywhere JavaScript runs - browsers, Node.js, Deno, Bun.

The key difference from JavaScript is the type system. TypeScript catches type errors at compile time, not at runtime, which prevents entire categories of bugs before your code ever runs.

It was created by Anders Hejlsberg, the same person behind C#. First released in 2012, it has since become one of the most widely used languages in front-end development and back-end development alike.

TypeScript doesn't replace JavaScript. It extends it. Every valid JavaScript file is also a valid TypeScript file.

TypeScript Basic Types

Primitive Types in TypeScript

TypeScript has seven primitive types inherited directly from JavaScript: string, number, boolean, null, undefined, symbol, and bigint.

let username: string = "Alice";
let age: number = 30;
let isActive: boolean = true;
let bigNum: bigint = 9007199254740991n;

null and undefined behave as distinct types when strict mode is enabled in tsconfig.json.

Special Types in TypeScript

Four types sit outside the normal type hierarchy and handle edge cases.

Type

What it does

any

Disables type checking entirely. Avoid when possible.

unknown

Like any, but type-safe. You must narrow it before using it.

void

Return type for functions that return nothing.

never

A value that never occurs - exhaustive checks, infinite loops, thrown errors.

unknown vs any is one of those distinctions that trips people up early on.

Use unknown when you receive data from an external source (API response, user input). Use any only as a last resort or during migration from JavaScript.

Type Inference in TypeScript

TypeScript infers types automatically when you assign a value at declaration.

let count = 0;       // inferred as number
let name = "Bob";    // inferred as string

You don't need to annotate every variable. The type inference engine handles most cases correctly.

Explicit annotations make sense for function signatures, complex objects, and anywhere the inferred type would be too broad.

TypeScript Type Annotations

Variable Type Annotation Syntax

Add a colon after the variable name, followed by the type.

let score: number;
let label: string = "draft";
let flags: boolean[] = [true, false];

Annotations separate declaration from assignment cleanly. Useful when you want to declare a variable before initializing it.

Function Parameter and Return Type Annotations

Annotate parameters directly in the function signature. The return type goes after the closing parenthesis.

function greet(name: string): string {
  return `Hello, ${name}`;
}

function logError(message: string): void {
  console.error(message);
}

Explicit return types are a good habit. They catch cases where a function accidentally returns undefined on one branch.

Object Type Annotations

Define the shape of an object inline or via a type alias.

let user: { name: string; age: number; isAdmin: boolean };

user = { name: "Alice", age: 28, isAdmin: false };

Inline object annotations work fine for one-off cases. For anything reused across your codebase, use an interface or type alias instead.

TypeScript Arrays and Tuples

Array Type Syntax in TypeScript

Two valid syntaxes. Both are equivalent.

let scores: number[] = [10, 20, 30];
let names: Array<string> = ["Alice", "Bob"];

number[] is more common in practice. Array<string> shows up more often in generic contexts.

Arrays in TypeScript are still standard JavaScript arrays under the hood - static typing just enforces what goes inside them.

Tuple Types in TypeScript

Tuples are fixed-length arrays where each position has a specific type.

let point: [number, number] = [42, 87];
let entry: [string, number] = ["Alice", 28];

Useful for returning multiple values from a function without creating a full object. React's useState hook returns a tuple, for example - [value, setter].

Optional tuple elements use ?. Rest elements work too, at the end of the tuple.

let flexible: [string, number?] = ["hello"];

TypeScript Interfaces

How to Define an Interface in TypeScript

An interface describes the shape of an object. It's a contract - any object assigned to that interface must match its structure.

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

Interfaces are one of the first things you'll use heavily in any real TypeScript project.

They show up constantly in API integration work, where you need to type-check incoming response data against a defined shape.

Optional and Readonly Properties in Interfaces

Mark optional properties with ?. Mark immutable properties with readonly.

interface Product {
  readonly id: number;
  name: string;
  description?: string;
}

readonly prevents reassignment after the object is created. ? means the property may or may not exist on the object.

Both are common in real-world TypeScript - especially when modeling API responses where some fields aren't always present.

Interface Extension in TypeScript

Interfaces can extend one or more other interfaces using the extends keyword.

interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

This keeps your type definitions DRY. Instead of redefining shared properties, you build on top of a base interface.

Multiple extension works too: interface C extends A, B {}.

Difference Between Interface and Type in TypeScript

Both describe object shapes. The differences are mostly subtle.

interface

type

Extension

extends

& intersection

Declaration merging

Yes

No

Union types

No

Yes

Primitive aliases

No

Yes

Use interface for object shapes and class contracts. Use type for union types, intersection types, and type aliases on primitives or complex combinations.

In practice, a lot of teams just pick one and stick with it.

TypeScript Type Aliases

How to Create a Type Alias

Use the type keyword to assign a name to any type expression.

type UserID = string;
type Coordinates = { x: number; y: number };

Type aliases work for primitives, objects, unions, intersections, and function signatures - more flexible than interfaces in that regard.

Union Types in TypeScript

A union type allows a value to be one of several types, separated by |.

type Status = "active" | "inactive" | "pending";
type ID = string | number;

String literal unions are especially useful for constraining function arguments to a fixed set of values.

Intersection Types in TypeScript

Intersection types combine multiple types into one using &. The result must satisfy all of them.

type Admin = User & { permissions: string[] };

Good for composing object types without full interface extension. Used heavily in utility-type patterns and generic constraints.

TypeScript Enums

Numeric Enums

Numeric enums auto-increment from 0 by default. You can set a custom starting value.

enum Direction {
  Up,     // 0
  Down,   // 1
  Left,   // 2
  Right   // 3
}

String Enums

Each member must have an explicit string value. No auto-incrementing.

enum Color {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE"
}

String enums are easier to debug - the values are readable in logs and network responses, unlike numeric enums.

Const Enums in TypeScript

const enum gets inlined at compile time. No enum object is emitted in the output JavaScript.

const enum Axis { X, Y, Z }
let a = Axis.X; // compiles to: let a = 0

Smaller output, faster execution. The tradeoff: no reverse mapping, and they don't work in all module systems.

TypeScript Functions

Function Type Signatures in TypeScript

Define the full type signature of a function using a type alias or inline annotation.

type Add = (a: number, b: number) => number;

const add: Add = (a, b) => a + b;

Optional and Default Parameters

Optional parameters use ?; default parameters use =. Optional must come after required.

function greet(name: string, greeting?: string): string {
  return `${greeting ?? "Hello"}, ${name}`;
}

function power(base: number, exp: number = 2): number {
  return base ** exp;
}

Rest Parameters in TypeScript

Rest parameters collect remaining arguments into a typed array.

function sum(...nums: number[]): number {
  return nums.reduce((a, b) => a + b, 0);
}

Function Overloads in TypeScript

Define multiple signatures for the same function, then implement with a general signature.

function format(value: string): string;
function format(value: number): string;
function format(value: string | number): string {
  return String(value);
}

Overloads improve type safety at call sites without duplicating logic. Useful when a function behaves differently depending on input type.

TypeScript Classes

Class Syntax in TypeScript

TypeScript classes extend JavaScript ES6 classes with type annotations, access modifiers, and strict property initialization.

class Person {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

Access Modifiers: public, private, protected

Modifier

Accessible from

public

Anywhere (default)

private

Only within the class

protected

Class and subclasses

TypeScript also supports the native # JavaScript private field syntax for runtime-enforced privacy.

Readonly and Static Members

readonly properties can only be set in the constructor. static members belong to the class itself, not instances.

class Config {
  static readonly MAX_RETRIES = 3;
  readonly id: string;

  constructor(id: string) {
    this.id = id;
  }
}

Abstract Classes in TypeScript

Abstract classes can't be instantiated directly. They define a base structure that subclasses must implement.

abstract class Shape {
  abstract area(): number;
}

class Circle extends Shape {
  constructor(private radius: number) { super(); }
  area(): number { return Math.PI * this.radius ** 2; }
}

Class Implementing an Interface

A class uses implements to declare it satisfies an interface contract. TypeScript enforces it at compile time.

interface Printable {
  print(): void;
}

class Report implements Printable {
  print() { console.log("Printing..."); }
}

TypeScript Generics

Generic Functions in TypeScript

Generics let you write functions that work with any type while preserving type safety.

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

identity<string>("hello"); // T = string
identity(42);              // T inferred as number

Generic Interfaces and Classes

Apply generics to interfaces and classes the same way - declare the type parameter after the name.

interface Box<T> {
  value: T;
}

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

Generic Constraints in TypeScript

Use extends to constrain what types a generic can accept.

function getLength<T extends { length: number }>(arg: T): number {
  return arg.length;
}

Works on strings, arrays, or any object with a length property. Rejects types that don't match.

Default Generic Types

Generics can have defaults, used when no type argument is passed.

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

TypeScript Utility Types

Partial, Required, and Readonly

Utility

What it does

Partial<T>

Makes all properties optional

Required<T>

Makes all properties required

Readonly<T>

Prevents mutation of all properties

type DraftUser = Partial<User>;
type FrozenUser = Readonly<User>;

Pick, Omit, and Exclude

Pick<T, K> selects a subset of properties; Omit<T, K> removes them; Exclude<T, U> filters union members.

type Preview = Pick<User, "name" | "email">;
type WithoutEmail = Omit<User, "email">;
type NonString = Exclude<string | number | boolean, string>;

Record Type in TypeScript

Record<K, V> creates an object type with keys of type K and values of type V.

type Scores = Record<string, number>;
const results: Scores = { alice: 95, bob: 87 };

Clean alternative to index signatures for fixed-value maps.

ReturnType and Parameters Utility Types

ReturnType<T> extracts the return type of a function. Parameters<T> extracts its parameter types as a tuple.

function fetchUser(): { id: number; name: string } { ... }

type UserResult = ReturnType<typeof fetchUser>; // { id: number; name: string }
type FetchArgs = Parameters<typeof fetchUser>;  // []

Useful when you need to reuse types from existing functions without duplicating the annotations.

TypeScript Type Narrowing

typeof and instanceof Guards

typeof narrows primitive types; instanceof narrows class instances.

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

in Operator for Type Narrowing

The in operator checks if a property exists on an object, narrowing to the type that contains it.

if ("email" in user) {
  console.log(user.email);
}

Discriminated Unions

A discriminated union uses a shared literal property to distinguish between union members. TypeScript narrows based on it automatically.

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

function area(s: Shape) {
  if (s.kind === "circle") return Math.PI * s.radius ** 2;
  return s.side ** 2;
}

Type Assertion in TypeScript

Type assertions tell TypeScript to treat a value as a specific type. Two syntaxes - prefer as in TSX files.

const input = document.getElementById("name") as HTMLInputElement;

Assertions bypass type checking. Use only when you're certain about the type - wrong assertions cause runtime bugs.

TypeScript Modules

Import and Export Syntax in TypeScript

TypeScript uses ES module syntax. Named exports allow multiple exports per file; default exports allow one.

// math.ts
export function add(a: number, b: number): number { return a + b; }
export const PI = 3.14159;

// main.ts
import { add, PI } from "./math";

Default Exports vs Named Exports

Default exports: one per file, imported with any name. Named exports: multiple per file, imported by exact name (or aliased with as).

Most style guides and linting rules prefer named exports - easier to refactor and better for linting in programming tools like ESLint.

Module Resolution in TypeScript

TypeScript resolves modules using either node or bundler strategy (set in tsconfig.json).

moduleResolution: "bundler" is the recommended setting for modern projects using Vite, esbuild, or similar tools.

Path aliases (@/components/*) require both tsconfig.json paths and bundler configuration to work correctly.

TypeScript Decorators

Class Decorators

A class decorator is a function applied to a constructor. Prefixed with @.

function sealed(constructor: Function) {
  Object.seal(constructor);
}

@sealed
class BankAccount { ... }

Decorators require "experimentalDecorators": true in tsconfig.json. The TC39 Stage 3 decorator proposal uses a different syntax - check which version your toolchain supports.

Method and Property Decorators

Method decorators receive the target object, method name, and property descriptor. Property decorators receive only the target and name.

function log(target: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${key}`);
    return original.apply(this, args);
  };
}

Used heavily in frameworks like NestJS and Angular for routing, validation, and dependency injection.

Parameter Decorators

Applied to individual function parameters. Receive the target, method name, and parameter index.

function Inject(token: string) {
  return (target: any, key: string, index: number) => {
    // register injection metadata
  };
}

Rarely used outside of framework internals. NestJS uses them for its Dependency Injection system.

TypeScript Configuration

tsconfig.json File Structure

tsconfig.json sits at the project root and controls compiler behavior. All TypeScript tooling reads from it automatically.

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "strict": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

Key Compiler Options in tsconfig.json

The most important options for day-to-day TypeScript work:

Option

Purpose

target

Output JavaScript version

module

Module system (ESNext, CommonJS)

strict

Enables all strict checks

outDir

Output directory for compiled files

rootDir

Root of source files

sourceMap

Generates .map files for debugging

noEmit

Type-check only, no output

strict Mode Settings

"strict": true enables a bundle of checks: noImplicitAny, strictNullChecks, strictFunctionTypes, strictPropertyInitialization, and more.

Turn it on from the start. Retrofitting strict mode into a large, existing codebase is painful.

module and target Options

target controls the output syntax; module controls how imports/exports are handled.

For modern projects: "target": "ES2020" and "module": "ESNext". For Node.js: "module": "CommonJS" or "NodeNext" depending on your Node version.

Path Aliases in tsconfig.json

Path aliases shorten import paths and remove ../../.. chains.

"paths": {
  "@components/*": ["src/components/*"],
  "@utils/*": ["src/utils/*"]
}

Also configure the same aliases in your bundler (Vite, Webpack, etc.) - tsconfig paths only affect type checking, not module resolution at build time.

TypeScript with JavaScript Interoperability

Declaration Files (.d.ts) in TypeScript

.d.ts files describe the types of JavaScript code without containing any implementation. TypeScript uses them to type-check against untyped JS libraries.

They're generated automatically via "declaration": true in tsconfig, or written manually for custom JS modules.

@types Packages and DefinitelyTyped

DefinitelyTyped is the community repository for TypeScript declaration files. Install type definitions via @types/ packages on npm.

npm install --save-dev @types/node @types/lodash

If a library already ships its own types (check package.json for a "types" field), you don't need a @types package.

allowJs and checkJs Options

allowJs: true lets TypeScript process .js files alongside .ts files - useful during gradual migration.

checkJs: true enables type checking in JavaScript files using JSDoc annotations. Good for projects that can't fully commit to TypeScript yet.

TypeScript Compilation and Tooling

How to Compile TypeScript with tsc

tsc is the TypeScript compiler. Install it via npm, then run it from the project root.

npm install --save-dev typescript
npx tsc         # compile using tsconfig.json
npx tsc --watch # watch mode
npx tsc --noEmit # type-check only

TypeScript with Webpack and Bundlers

Use ts-loader or babel-loader with @babel/preset-typescript to process TypeScript in Webpack.

Vite and esbuild handle TypeScript out of the box - no loader config needed. They strip types without checking them, so run tsc --noEmit separately in your CI pipeline or as part of your build pipeline.

TypeScript with Node.js

For Node.js projects, set "module": "CommonJS" or use "module": "NodeNext" with .mts/.cts extensions.

ts-node runs TypeScript directly in Node without a compile step - useful for scripts and development. For production, compile to JavaScript first.

TypeScript with React (TSX)

Enable JSX support with "jsx": "react-jsx" in tsconfig. Use .tsx extension for files containing JSX.

interface ButtonProps {
  label: string;
  onClick: () => void;
}

const Button = ({ label, onClick }: ButtonProps) => (
  <button onClick={onClick}>{label}</button>
);

TypeScript and React work well together - typed props eliminate an entire class of component bugs that would otherwise surface at runtime in your web apps.

FAQ on TypeScript Cheat Sheets

What is the difference between TypeScript and JavaScript?

TypeScript is a statically typed superset of JavaScript that compiles to plain JS.

JavaScript has no type system; TypeScript adds type annotations, interfaces, and compile-time error checking. Every valid JavaScript file is also valid TypeScript.

Do I need to learn JavaScript before TypeScript?

Yes. TypeScript builds directly on JavaScript syntax and behavior.

Without a solid JavaScript foundation, TypeScript's type system won't make much sense. Learn JS fundamentals first, then layer on type annotations and interfaces.

What does strict mode do in TypeScript?

"strict": true in tsconfig.json enables a bundle of safety checks including strictNullChecks and noImplicitAny.

It prevents entire categories of bugs. Turn it on from day one - retrofitting it into an existing codebase is significantly harder.

What is type inference in TypeScript?

Type inference means TypeScript automatically determines a variable's type from its assigned value, without explicit annotation.

let count = 0 is inferred as number. You only need manual annotations for function signatures, complex objects, or ambiguous cases.

When should I use interface vs type in TypeScript?

Use interface for object shapes and class contracts. Use type for union types, intersections, and primitive aliases.

Both are largely interchangeable for object definitions. Most teams pick one convention and stay consistent throughout the codebase.

What are TypeScript utility types?

Built-in generic types that transform existing types. Partial<T> makes all properties optional; Pick<T, K> selects specific properties; Readonly<T> prevents mutation.

They remove repetitive type definitions and keep your code DRY without custom logic.

What is a .d.ts file in TypeScript?

A declaration file that describes the types of a JavaScript module without containing any implementation.

TypeScript uses .d.ts files to type-check against untyped JS libraries. They're auto-generated via "declaration": true in tsconfig.json or sourced from @types/ packages.

How do TypeScript generics work?

Generics let you write reusable functions and classes that work with any type while preserving type safety.

function identity<T>(val: T): T works for strings, numbers, or objects. The type is inferred from the argument or passed explicitly at the call site.

What is tsconfig.json and why does it matter?

tsconfig.json controls how the TypeScript compiler processes your project - output target, module system, strict checks, path aliases, and more.

Every TypeScript toolchain reads from it. Without it, the compiler falls back to defaults that rarely match real project needs.

Can TypeScript be used with React and Node.js?

Yes, widely. For React, enable "jsx": "react-jsx" in tsconfig and use .tsx files.

For back-end development with Node.js, set "module": "CommonJS" or "NodeNext". Both ecosystems have strong type definition support via DefinitelyTyped.