TypeScript: The Practical Bits
Just enough TypeScript to ship faster and safer — types, unions, generics, and the parts everyone actually uses.
Why TypeScript
TypeScript is JavaScript with optional static types. You write code that looks like JS, add type annotations where you want, and the TypeScript compiler (tsc) checks types at compile time and emits plain JS. At runtime, types are GONE — they exist purely to help you and your IDE catch mistakes earlier.
Most large JavaScript codebases now use TypeScript. The reasons: autocomplete that actually works, refactors that don't break things in production, and self-documenting function signatures.
Basic types
let name: string = "Ada";
let age: number = 36;
let active: boolean = true;
let tags: string[] = ["js", "ts"];
let pair: [number, string] = [1, "one"]; // tuple
let anything: unknown = JSON.parse("{}"); // \"any\" but safer
function add(a: number, b: number): number {
return a + b;
}You can almost always omit type annotations on variables — TypeScript infers them from the assigned value. Annotate function parameters and return types; let inference handle the rest.
Type aliases and interfaces
type User = {
id: string;
name: string;
admin?: boolean; // optional
};
interface Post {
id: string;
title: string;
authorId: string;
}
function greet(u: User): string {
return u.admin ? `Welcome back, boss ${u.name}` : `Hi ${u.name}`;
}`type` and `interface` are mostly interchangeable for object shapes. Use `interface` when you want declaration merging or class-y inheritance; use `type` for everything else (unions, primitives, mapped types).
Union types — the killer feature
type Status = "loading" | "success" | "error";
function render(s: Status) {
if (s === "loading") return "...";
if (s === "error") return "!";
return ":)";
}
// Discriminated unions — the cleanest pattern in TS
type Result<T> =
| { ok: true; value: T }
| { ok: false; error: string };
function useResult(r: Result<number>) {
if (r.ok) {
console.log(r.value); // TS knows .value exists here
} else {
console.log(r.error); // and .error exists here
}
}Generics
Generics let you write code that works with any type, while keeping type information. Think of `<T>` as a type parameter — a placeholder filled in at the call site.
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const n = first([1, 2, 3]); // n: number | undefined
const s = first(["a", "b"]); // s: string | undefinedWorking with arrays of objects
type Order = { id: string; total: number; status: "open" | "paid" };
function totalRevenue(orders: Order[]): number {
return orders
.filter(o => o.status === "paid")
.reduce((sum, o) => sum + o.total, 0);
}Things you can skip at first
- Conditional types (`T extends U ? X : Y`).
- Mapped types (`{ [K in keyof T]: ... }`).
- Template literal types (`\`prefix-\${string}\``).
- All the utility types beyond `Partial`, `Pick`, and `Omit`.