Back to Notes

TypeScript

TypeScript

Typed superset of JavaScript. Catches errors at compile time. Standard for Next.js, React, Node.js in 2026. Your portfolio (vectorbuilds.dev) is TypeScript + Next.js.

npx tsc --init     # create tsconfig.json
npx tsc            # compile
npx tsc --watch    # watch mode

Basic Types

// Primitives
let name: string = "Alice";
let age: number = 30;
let active: boolean = true;
let nothing: null = null;
let notSet: undefined = undefined;

// Arrays
let nums: number[] = [1, 2, 3];
let strs: Array<string> = ["a", "b"];

// Tuple — fixed-length, typed array
let pair: [string, number] = ["Alice", 30];

// Enum
enum Direction { Up, Down, Left, Right }
let dir: Direction = Direction.Up;

// any — escape hatch (avoid)
let anything: any = 42;
anything = "now a string";  // no error

// unknown — safer alternative to any
let val: unknown = fetchData();
if (typeof val === "string") {
    console.log(val.toUpperCase()); // type-narrowed
}

// never — function that never returns
function fail(msg: string): never {
    throw new Error(msg);
}

Interfaces & Type Aliases

// Interface — for objects and classes
interface User {
    id: number;
    name: string;
    email?: string;          // optional
    readonly createdAt: Date; // readonly
}

// Type alias — for anything
type ID = string | number;
type Point = { x: number; y: number };
type Callback = (err: Error | null, result?: string) => void;

// When to use which:
// Interface: object shapes, extendable, class contracts
// Type: unions, intersections, primitives, utility types

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

// Type intersection (merge)
type AdminUser = User & { permissions: string[] };

Union & Intersection Types

// Union — one of
type Status = "pending" | "active" | "inactive";
type ID = string | number;

function process(id: string | number) {
    if (typeof id === "string") {
        return id.toUpperCase(); // narrowed to string
    }
    return id * 2;               // narrowed to number
}

// Intersection — combine all
type HasName = { name: string };
type HasAge  = { age: number };
type Person  = HasName & HasAge;  // { name: string; age: number }

Generics

// Generic function
function identity<T>(arg: T): T {
    return arg;
}
identity<string>("hello");
identity(42);  // inferred: T = number

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

type UsersResponse = ApiResponse<User[]>;

// Generic constraints
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

const user: User = { id: 1, name: "Alice", createdAt: new Date() };
getProperty(user, "name");   // ✓ string
getProperty(user, "foo");    // ✗ compile error

Utility Types

// Partial — all fields optional
type PartialUser = Partial<User>;                // { id?: number; name?: string; ... }

// Required — all fields required
type RequiredUser = Required<PartialUser>;

// Pick — subset of fields
type UserPreview = Pick<User, "id" | "name">;

// Omit — remove fields
type CreateUser = Omit<User, "id" | "createdAt">;

// Readonly — immutable
type ImmutableUser = Readonly<User>;

// Record — key-value map type
type UserMap = Record<string, User>;             // { [key: string]: User }
type StatusCount = Record<Status, number>;       // { pending: number; ... }

// ReturnType — extract return type
function getUser() { return { id: 1, name: "Alice" }; }
type GetUserReturn = ReturnType<typeof getUser>;  // { id: number; name: string }

// Parameters — extract function params
type GetUserParams = Parameters<typeof getUser>;  // []

Type Narrowing

// typeof
function padLeft(value: string, padding: string | number) {
    if (typeof padding === "number") {
        return " ".repeat(padding) + value;
    }
    return padding + value;
}

// instanceof
function format(value: Date | string) {
    if (value instanceof Date) {
        return value.toISOString();
    }
    return value;
}

// in operator
interface Cat { meow(): void }
interface Dog { bark(): void }

function makeSound(animal: Cat | Dog) {
    if ("meow" in animal) {
        animal.meow();  // narrowed to Cat
    } else {
        animal.bark();
    }
}

// Discriminated union — best pattern
type Shape =
    | { kind: "circle"; radius: number }
    | { kind: "rect"; width: number; height: number };

function area(shape: Shape) {
    switch (shape.kind) {
        case "circle": return Math.PI * shape.radius ** 2;
        case "rect":   return shape.width * shape.height;
    }
}

as const — Literal Types

// Without as const: type is string[]
const colors = ["red", "green", "blue"];

// With as const: type is readonly ["red", "green", "blue"]
const COLORS = ["red", "green", "blue"] as const;
type Color = typeof COLORS[number];  // "red" | "green" | "blue"

// Object literal types
const config = {
    endpoint: "https://api.example.com",
    timeout: 5000
} as const;
// config.endpoint is "https://api.example.com", not string

TypeScript in React (common patterns)

// Component props
interface ButtonProps {
    label: string;
    onClick: () => void;
    variant?: "primary" | "secondary";
    children?: React.ReactNode;
}

const Button: React.FC<ButtonProps> = ({ label, onClick, variant = "primary" }) => (
    <button onClick={onClick} className={variant}>{label}</button>
);

// useState with type
const [user, setUser] = useState<User | null>(null);
const [items, setItems] = useState<string[]>([]);

// useRef
const inputRef = useRef<HTMLInputElement>(null);

// Generic fetch hook
async function fetchData<T>(url: string): Promise<T> {
    const res = await fetch(url);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return res.json() as Promise<T>;
}

const users = await fetchData<User[]>("/api/users");

tsconfig.json Key Options

{
    "compilerOptions": {
        "target": "ES2022",           // output JS version
        "module": "ESNext",           // module system
        "strict": true,               // enable all strict checks (always true)
        "noImplicitAny": true,        // error on implicit any
        "strictNullChecks": true,     // null/undefined must be explicit
        "paths": {
            "@/*": ["./src/*"]        // import aliases
        },
        "baseUrl": ".",
        "outDir": "./dist",
        "jsx": "preserve"             // for React/Next.js
    }
}

Always use strict: true. It enables noImplicitAny, strictNullChecks, and others.


Interview Talking Points

  • "TypeScript's discriminated unions are better than class hierarchies for modeling state — you get exhaustive type checking in switch statements at compile time."
  • "unknown is the safe any — you can't use it without a type guard, so it forces you to think about what you're handling."
  • "Utility types like Partial<T>, Pick<T,K>, Omit<T,K> are the first tools I reach for when I need to derive types from existing ones — avoids repetition and stays in sync with the base type."

Related

  • [[FrontEnd/React/React]] — TypeScript in React components
  • [[FrontEnd/Java script/JavaScript]] — TypeScript builds on JS
  • [[FrontEnd/FrontEnd Topics]] — frontend index