iZONE

TypeScript Cheatsheet

A practical guide to TypeScript types, generics, and modules you can use right away.

Resources

Basics: Types

TypeScript lets you say what kind of value a variable holds. Do that, and it'll warn you the moment something doesn't add up — before your code even runs.

the everyday types

TypeScript

// type goes after the variable name
let username: string  = "Alice";
let age:      number  = 30;
let isActive: boolean = true;
let score:    number  = 9.5;

null and undefined

TypeScript

// use | to allow null or undefined
let middleName: string | null = null;
// has no middle name

let nickname: string | undefined;
// not set yet

any and unknown

TypeScript

let wild: any = "hello";
wild = 42;
// ✅ allowed — but no safety net

let safe: unknown = "hello";

// can't use directly ❌
// safe.toUpperCase();

// check type first ✅
if (typeof safe === "string") {
  safe.toUpperCase();
}

Prefer `unknown` over `any`. With `unknown`, TypeScript forces you to check the type first — keeping you safe.

literal types

TypeScript

// only these four words are allowed
let direction: "left" | "right" | "up" | "down";

direction = "left";
// ✅ allowed

direction = "diagonal";
// ❌ not in the list

// only dice values 1–6
let roll: 1 | 2 | 3 | 4 | 5 | 6;

Arrays & Tuples

Arrays hold a list of values all the same type. Tuples are stricter — a fixed list where each position has its own specific type.

typed arrays

TypeScript

let scores: number[] = [10, 20, 30];
let names:  string[] = ["Alice", "Bob"];
let flags:  boolean[] = [true, false];

// same thing, different syntax
let scores2: Array<number> = [10, 20, 30];

tuples — fixed positions

TypeScript

// position 0 = string, position 1 = number
let person: [string, number] = ["Alice", 30];

let name = person[0];
// "Alice" — TypeScript knows it's a string

let age = person[1];
// 30 — TypeScript knows it's a number

// real-world: [x, y] coordinates
let point: [number, number] = [10, 20];

Use tuples when order and meaning of each position matters — like `[x, y]` coordinates or `[status, message]` pairs.

Functions

You can type both what goes in and what comes out of a function. TypeScript then catches it immediately if you pass the wrong thing or forget to return something.

typed function basics

TypeScript

// name must be string, returns string
function greet(name: string): string {
  return "Hello, " + name;
}

// a and b must be numbers, returns number
function add(a: number, b: number): number {
  return a + b;
}

// arrow function — same rules
const multiply = (
  a: number,
  b: number
): number => a * b;

optional and default parameters

TypeScript

// optional with ?
function greet(
  name: string,
  greeting?: string
): string {
  return (greeting ?? "Hello") + ", " + name;
}

greet("Alice");
// "Hello, Alice"

greet("Alice", "Hi");
// "Hi, Alice"

// default value
function greetDefault(
  name: string,
  greeting: string = "Hello"
): string {
  return greeting + ", " + name;
}

void — functions that return nothing

TypeScript

function logMessage(msg: string): void {
  console.log(msg);
  // just prints — returns nothing
}

rest parameters

TypeScript

function sum(...numbers: number[]): number {
  return numbers.reduce((total, n) => total + n, 0);
}

sum(1, 2, 3);
// 6

sum(10, 20, 30, 40);
// 100

Interfaces & Type Aliases

Instead of repeating the same object shape everywhere, give it a name. Then use that name as a type anywhere you need it.

interface — describe an object's shape

TypeScript

interface User {
  id:     number;
  name:   string;
  email?: string;
  // optional — can be missing
}

const user1: User = {
  id: 1, name: "Alice"
};
// ✅ no email needed

const user2: User = {
  id: 2, name: "Bob", email: "[email protected]"
};
// ✅ with email

type alias — name any type

TypeScript

// name a union type
type ID = string | number;

let userId: ID = "abc123"; // ✅
let postId: ID = 42;       // ✅

// name an object shape
type Point = { x: number; y: number };

const origin: Point = { x: 0, y: 0 };

Use `interface` for object shapes you may extend later. Use `type` for unions, primitives, and computed types.

extending — build on top of another type

TypeScript

interface Animal {
  name: string;
}

// Dog gets everything Animal has, plus 'breed'
interface Dog extends Animal {
  breed: string;
}

const dog: Dog = {
  name: "Rex",
  breed: "Labrador"
};
// ✅

Classes

A class is a template for creating objects. TypeScript adds type-checking on top — so you can't accidentally use a class the wrong way.

basic class

TypeScript

class Animal {
  name: string; // typed property

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

  speak(): string {
    return this.name + " makes a sound.";
  }
}

const cat = new Animal("Cat");
cat.speak();
// "Cat makes a sound."

access modifiers

TypeScript

class BankAccount {
  public   owner:   string;
  private  balance: number;
  readonly id:      string;

  constructor(
    owner: string,
    start: number,
    id: string
  ) {
    this.owner   = owner;
    this.balance = start;
    this.id      = id;
  }

  deposit(amount: number): void {
    this.balance += amount;
    // ✅ inside the class — allowed
  }
}

const acc = new BankAccount("Alice", 100, "ACC-001");
acc.owner;
// ✅ public

// acc.balance
// ❌ private — TypeScript error

inheritance

TypeScript

class Animal {
  constructor(public name: string) {}

  speak(): string {
    return this.name + " makes a sound.";
  }
}

class Dog extends Animal {
  constructor(
    name: string,
    public breed: string
  ) {
    super(name);
    // run Animal's constructor first
  }

  // override parent method
  speak(): string {
    return this.name + " barks!";
  }
}

const dog = new Dog("Rex", "Labrador");
dog.speak()
// "Rex barks!"

dog.breed
// "Labrador"

Unions & Intersections

Unions let a value be one of several types. Intersections combine multiple types into one. Both are used constantly in real TypeScript code.

union — this OR that

TypeScript

// id can be a number or a string
let id: string | number;

id = 101;   // ✅
id = "abc"; // ✅
id = true;  // ❌ not in the union

function printId(id: string | number): void {
  console.log("ID: " + id);
}

discriminated union — safe branching

TypeScript

type Circle = {
  kind: "circle";
  radius: number
};

type Rectangle = {
  kind: "rectangle";
  width: number;
  height: number
};

type Shape = Circle | Rectangle;

function getArea(shape: Shape): number {
  if (shape.kind === "circle") {
    // TypeScript knows it's Circle here
    return Math.PI * shape.radius ** 2;
  }
  // TypeScript knows it's Rectangle here
  return shape.width * shape.height;
}

Use a discriminated union when you have different object shapes in the same variable — it gives full type safety inside each branch.

intersection — this AND that

TypeScript

type HasName  = { name: string };
type HasEmail = { email: string };

// Contact must have BOTH name AND email
type Contact = HasName & HasEmail;

const contact: Contact = {
  name:  "Alice",
  // required by HasName
  email: "[email protected]",
  // required by HasEmail
};

Type Narrowing

When a value could be multiple types, TypeScript narrows it down inside if checks — so you always get the right methods and no false errors.

typeof — check the type at runtime

TypeScript

function printValue(value: string | number): void {
  if (typeof value === "string") {
    // TypeScript knows it's string here
    console.log(value.toUpperCase());
  } else {
    // TypeScript knows it's number here
    console.log(value.toFixed(2));
  }
}

instanceof — check for a class

TypeScript

function formatDate(value: string | Date): string {
  if (value instanceof Date) {
    // TypeScript knows it's Date here
    return value.toISOString();
  }
  // TypeScript knows it's string here
  return value;
}

Generics

Generics let you write one function or interface that works with any type — while still keeping full type safety. Write it once, use it everywhere.

the problem generics solve

TypeScript

// ❌ without generics — repeat for every type
function wrapString(value: string): string[] {
  return [value];
}
function wrapNumber(value: number): number[] {
  return [value];
}

// ✅ with generics — one function for all types
function wrap<T>(value: T): T[] {
  return [value];
}

wrap("hello");
// T = string → string[]

wrap(42);
// T = number → number[]

wrap(true);
// T = boolean → boolean[]

constrained generics

TypeScript

// T must have a 'length' property
function longest<T extends { length: number }>(
  a: T,
  b: T
): T {
  return a.length >= b.length ? a : b;
}

longest("alice", "bob");
// ✅ strings have .length

longest([1, 2, 3], [1, 2]);
// ✅ arrays have .length

// longest(1, 2);
// ❌ numbers have no .length

generic with default type

TypeScript

// T defaults to string if not specified
interface Box<T = string> {
  value: T;
  label: string;
}

const strBox: Box = {
  value: "hello",
  label: "greeting"
};
// T = string (default used)

const numBox: Box<number> = {
  value: 42,
  label: "score"
};
// T = number (explicitly set)

generic interfaces

TypeScript

interface ApiResponse<T> {
  data:    T;
  success: boolean;
  message: string;
}

// data is a User object
type UserResponse = ApiResponse<{
  id: number;
  name: string;
}>;

// data is a list of numbers
type ScoresResponse = ApiResponse<number[]>;

Utility Types

TypeScript comes with ready-made helpers that transform your types. No imports needed — they just work out of the box.

Partial — make all fields optional

TypeScript

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

function updateUser(
  id: number,
  changes: Partial<User>
): void {
  // changes can have any User fields
}

updateUser(1, { name: "Bob" });
// ✅ only updating name

updateUser(1, { email: "[email protected]" });
// ✅ only updating email

Required — make all fields required

TypeScript

interface Config {
  host?:    string;
  port?:    number;
  debug?:   boolean;
}

// after setup, all fields must be present
function startServer(config: Required<Config>): void {
  console.log(config.host);
  // ✅ TypeScript knows this exists
}

startServer({ host: "localhost", port: 3000, debug: false });
// ✅

startServer({ host: "localhost" });
// ❌ port and debug are now required

Readonly — prevent changes

TypeScript

interface Config {
  apiUrl:  string;
  timeout: number;
}

const config: Readonly<Config> = {
  apiUrl:  "https://api.example.com",
  timeout: 3000,
};

// config.apiUrl = "other";
// ❌ TypeScript error — read-only

Pick & Omit — select or remove fields

TypeScript

interface User {
  id:       number;
  name:     string;
  email:    string;
  password: string;
}

// keep only id and name
type PublicProfile = Pick<User, "id" | "name">;
// { id: number; name: string }

// remove password
type SafeUser = Omit<User, "password">;
// { id: number; name: string; email: string }

Record — build a key-value object type

TypeScript

// any string key, number value
const scores: Record<string, number> = {
  Alice: 95,
  Bob:   87,
};

// only specific keys allowed
type Role = "admin" | "editor" | "viewer";
type Permissions = Record<Role, boolean>;

const perms: Permissions = {
  admin:  true,
  editor: true,
  viewer: false,
};

ReturnType — get a function's return type

TypeScript

function getUser() {
  return { id: 1, name: "Alice", active: true };
}

// derive the type from the function — stays in sync
type User = ReturnType<typeof getUser>;
// { id: number; name: string; active: boolean }

// useful with API functions
async function fetchConfig() {
  return { apiUrl: "https://...", timeout: 3000 };
}

type Config = Awaited<ReturnType<typeof fetchConfig>>;
// { apiUrl: string; timeout: number }

Enums

Enums give friendly names to a fixed set of values. Your code becomes easier to read and harder to misuse — no more magic strings scattered everywhere.

string enums

TypeScript

enum Status {
  Pending  = "PENDING",
  Active   = "ACTIVE",
  Inactive = "INACTIVE",
}

function activate(status: Status): void {
  console.log("Status:", status);
}

activate(Status.Active);
// ✅ "Status: ACTIVE"

// activate("ACTIVE");
// ❌ must use the enum

as const — modern alternative to enums

TypeScript

const STATUS = {
  Pending:  "PENDING",
  Active:   "ACTIVE",
  Inactive: "INACTIVE",
} as const;

// derive type automatically
type Status =
  typeof STATUS[keyof typeof STATUS];
// "PENDING" | "ACTIVE" | "INACTIVE"

function activate(status: Status): void {
  console.log(status);
}

activate(STATUS.Active); // ✅
activate("ACTIVE");      // ✅ plain strings work too

Many teams prefer `as const` over `enum` — it works better with TypeScript's type tools and plain strings are accepted.

Modules — Import & Export

Split your code into separate files and share what's needed between them. TypeScript adds one extra trick: type-only imports that disappear completely at runtime.

exporting from a file

TypeScript

// math.ts
export const PI = 3.14159;

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

// export a type too
export type Point = { x: number; y: number };

// default export
export default function multiply(
  a: number,
  b: number
) {
  return a * b;
}

importing from another file

TypeScript

// default — no {}
import multiply from "./math";

// named — need {}
import { add, PI } from "./math";

// import everything as one object
import * as MathUtils from "./math";
MathUtils.add(1, 2) // 3
MathUtils.PI        // 3.14159

// type-only — removed at runtime
import type { Point } from "./math";

Always use `import type` when importing only types — it signals intent and has zero impact on the output bundle.

Advanced Types

These tools let you build new types from existing ones — so your types stay in sync with your code automatically. Most of these are intermediate to advanced.

keyof — get the keys of a type

TypeScript

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

type UserKeys = keyof User;
// "id" | "name" | "email"

// safe property lookup
function getField<T, K extends keyof T>(
  obj: T,
  key: K
): T[K] {
  return obj[key];
}

const user = { id: 1, name: "Alice", email: "[email protected]" };

getField(user, "name");
// ✅ "Alice"

// getField(user, "role");
// ❌ "role" is not a key of User

typeof — get the type of a value

TypeScript

const config = {
  apiUrl:  "https://api.example.com",
  timeout: 3000,
  debug:   false,
};

// derive type from value — always in sync
type Config = typeof config;
// {
//   apiUrl:  string;
//   timeout: number;
//   debug:   boolean;
// }

mapped types — transform every field

TypeScript

// make every field in T a string
type StringVersion<T> = {
  [Key in keyof T]: string;
};

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

type UserErrors = StringVersion<User>;
// {
//   id:   string;
//   name: string;
//   age:  string;
// }
// useful for form error messages

conditional types — type-level if/else

TypeScript

// if T is string → true, otherwise → false
type IsString<T> = T extends string ? true : false;

type A = IsString<string>; // true
type B = IsString<number>; // false

// unwrap a Promise to get the value inside
type Unwrap<T> =
  T extends Promise<infer Inner> ? Inner : T;

type Result = Unwrap<Promise<string>>;
// string

type Same = Unwrap<number>;
// number — not a Promise, returned as-is

tsconfig Essentials

The tsconfig.json file tells TypeScript how strict to be and what kind of JavaScript to output. And honestly, getting this right at the start saves a lot of pain later.

a good starting config

JSON

{
  "compilerOptions": {
    "target": "ES2022",
    // what version of JS to output

    "module": "ESNext",
    // use modern import/export

    "moduleResolution": "bundler",
    // let your bundler handle imports

    "lib": ["ES2022", "DOM"],
    // available APIs

    "strict": true,
    // all safety checks ← most important

    "outDir": "./dist",
    // where compiled JS files go

    "sourceMap": true
    // makes debugging easier
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

what strict: true turns on

JSON

"strictNullChecks": true
// null/undefined aren't secretly everywhere

"noImplicitAny": true
// must write the type — no silent 'any'

"strictFunctionTypes": true
// function params checked more carefully

"alwaysStrict": true
// adds "use strict" to every output file

Always start with `strict: true`. It's much easier than adding individual rules later to an existing codebase.

Was this helpful?

No login required to share feedback

More Cheatsheets

Keep your reference handy

Explore more zero-to-hero cheatsheets for the tools you use daily.