Skip to main content

TypeScript Quick Reference

A syntax refresher for when you've been away from TS for a while.

Basic Types

let id: number = 5;
let name: string = "Al";
let active: boolean = true;
let tags: string[] = ["a", "b"];
let tuple: [string, number] = ["age", 30];
let anything: any = "avoid me";
let unknown: unknown = "safer than any";
let nothing: void = undefined; // typically a function return type
let never: never; // a function that never returns (throws/loops forever)

// Literal types
let direction: "up" | "down" | "left" | "right";

Interfaces vs Type Aliases

// Interface — best for objects/classes, can be extended & merged
interface User {
id: number;
name: string;
email?: string; // optional
readonly createdAt: Date; // can't be reassigned after creation
}

interface Admin extends User {
permissions: string[];
}

// Type alias — more flexible, can represent unions, primitives, tuples
type ID = number | string;
type Point = { x: number; y: number };
type Callback = (data: string) => void;

// Merging: interfaces of the same name auto-merge, types do not
interface Box { width: number }
interface Box { height: number } // merges into one Box

Rule of thumb: use interface for object shapes you might extend; use type for unions, tuples, or function signatures.

Functions

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

// Optional & default params
function greet(name: string, greeting: string = "Hello"): string {
return `${greeting}, ${name}`;
}

function log(message?: string): void {
console.log(message ?? "no message");
}

// Arrow function with types
const multiply = (a: number, b: number): number => a * b;

// Function type
type MathOp = (a: number, b: number) => number;
const divide: MathOp = (a, b) => a / b;

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

Union & Intersection Types

// Union — one of several types
let value: string | number;

function printId(id: string | number) {
if (typeof id === "string") {
console.log(id.toUpperCase()); // narrowed to string here
} else {
console.log(id.toFixed(2)); // narrowed to number here
}
}

// Intersection — combine multiple types into one
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged; // must have both name AND age

Generics

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

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

// Generic interfaces
interface ApiResponse<T> {
data: T;
error: string | null;
}

const response: ApiResponse<User> = { data: user, error: null };

// Generic with constraints
function getLength<T extends { length: number }>(item: T): number {
return item.length;
}

// Multiple type params
function pair<A, B>(a: A, b: B): [A, B] {
return [a, b];
}

// Common generic utility patterns
class Box<T> {
constructor(private value: T) {}
get(): T {
return this.value;
}
}

Utility Types

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

Partial<User>; // all properties optional
Required<User>; // all properties required
Readonly<User>; // all properties readonly
Pick<User, "id" | "name">; // only id and name
Omit<User, "email">; // everything except email
Record<string, number>; // { [key: string]: number }
Exclude<"a" | "b" | "c", "a">; // "b" | "c"
Extract<"a" | "b" | "c", "a" | "d">; // "a"
ReturnType<typeof myFunc>; // the return type of a function
Parameters<typeof myFunc>; // tuple of a function's param types
NonNullable<string | null | undefined>; // string

Type Narrowing

// typeof
if (typeof value === "string") { /* ... */ }

// instanceof
if (error instanceof Error) { /* ... */ }

// in
if ("email" in user) { /* ... */ }

// Truthiness / equality checks also narrow
if (value != null) { /* value is not null or undefined */ }

// Custom type guards
function isUser(obj: unknown): obj is User {
return typeof obj === "object" && obj !== null && "id" in obj;
}

Enums

enum Direction {
Up,
Down,
Left,
Right,
}
// Direction.Up === 0

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

// const enum — inlined at compile time, no runtime object
const enum Level {
Low,
High,
}

// Often preferred alternative: a union of string literals
type Status2 = "ACTIVE" | "INACTIVE";

Classes

class Animal {
private name: string;
protected age: number = 0;
readonly species: string;

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

speak(): string {
return `${this.name} makes a sound.`;
}
}

// Shorthand constructor properties
class Point {
constructor(public x: number, public y: number) {}
}

// Implementing an interface
interface Shape {
area(): number;
}

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

// Abstract classes
abstract class Base {
abstract doWork(): void; // must be implemented by subclasses
run(): void {
console.log("running...");
}
}

Type Assertions

const el = document.getElementById("app") as HTMLDivElement;
const el2 = <HTMLDivElement>document.getElementById("app"); // not allowed in .tsx files

// Non-null assertion — "trust me, this isn't null/undefined"
const value = maybeUndefined!;

// as const — lock down literal types
const config = { mode: "production" } as const; // mode: "production", not string
const tuple = [1, 2] as const; // readonly [1, 2]

Arrays & Tuples

let list: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3]; // equivalent

let tuple: [string, number] = ["age", 30];
let optionalTuple: [string, number?] = ["age"];
let namedTuple: [name: string, age: number] = ["Al", 30]; // labels for readability

// Readonly array
let readonlyList: readonly number[] = [1, 2, 3];

Modules & Type-only Imports

// Type-only import — erased at compile time, no runtime cost
import type { User } from "./types";
import { type User, getUser } from "./types"; // mixed

export type { User };

Mapped & Conditional Types (good to recognize)

// Mapped type
type Optional<T> = {
[K in keyof T]?: T[K];
};

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

// Template literal types
type EventName = `on${Capitalize<"click" | "hover">}`;
// "onClick" | "onHover"

Common Gotchas

// `any` disables type checking entirely — prefer `unknown` and narrow it
function process(data: unknown) {
if (typeof data === "string") {
console.log(data.toUpperCase()); // safe
}
}

// Excess property checks only apply to object literals directly assigned
interface Config { name: string }
const c: Config = { name: "x", extra: 1 }; // ERROR: excess property
const obj = { name: "x", extra: 1 };
const c2: Config = obj; // OK — no excess check via a variable

// Structural typing — TS cares about shape, not declared type name
interface Point { x: number; y: number }
function log(p: Point) { console.log(p); }
log({ x: 1, y: 2, z: 3 }); // OK, extra props allowed when not a literal

// Non-null assertion (!) doesn't actually check anything at runtime
const el = document.getElementById("missing")!; // could still be null!

tsconfig.json Essentials

{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"noUnusedLocals": true,
"noImplicitAny": true
}
}

strict: true turns on the full set of strict checks (noImplicitAny, strictNullChecks, etc.) — recommended for all new projects.