Phase A: TypeScript Foundations (In-Repo Lessons)
Maps to: Blueprint Phase A
Goal: Write small programs with correct types so later APIs and React/Next code stay maintainable.
Table of Contents
- Prerequisites
- Lesson 1: Tooling and your first types
- Lesson 2: Functions and narrowing
- Lesson 3: Objects, arrays, and immutability patterns
- Lesson 4: Unions, intersections, and literals
- Lesson 5: Generics (the practical slice)
- Lesson 6: Classes, interfaces, and guards
- Self-check exercises
- Next step
Prerequisites
- Comfortable running commands in a terminal.
- Basic programming (variables, loops). If you only know Python. That is enough to start.
Install Node.js (using nvm is recommended so you can switch versions per project). Then:
mkdir ts-phase-a && cd ts-phase-a
npm init -y
npm install -D typescript ts-node @types/node
npx tsc --init --rootDir src --outDir dist --esModuleInterop --resolveJsonModule --strict
mkdir -p src
Create src/lesson01.ts and run with npx ts-node src/lesson01.ts (or compile with npx tsc and run node dist/lesson01.js).
Lesson 1: Tooling and your first types
TypeScript adds a type layer that disappears at runtime. The compiler catches mistakes early.
Core primitives: string, number, boolean, null, undefined, bigint, symbol.
const userName: string = "Ada";
let score: number = 0;
score = 10;
Type inference: when the compiler can prove a type, you may omit annotations.
const inferred = 42; // number
Arrays and tuples:
const tags: string[] = ["ml", "typescript"];
const pair: [string, number] = ["epoch", 1];
Optional properties:
type User = { id: string; displayName?: string };
const u: User = { id: "u1" };
Exercise: Define a Book type with title, year, and optional isbn. Create two values: one with isbn, one without.
Lesson 2: Functions and narrowing
Parameter and return types:
function add(a: number, b: number): number {
return a + b;
}
Union types mean “one of several shapes.” You often narrow with checks:
type Result = { ok: true; value: number } | { ok: false; error: string };
function unwrap(r: Result): number {
if (r.ok) return r.value;
throw new Error(r.error);
}
Optional chaining and nullish coalescing:
const len = u.displayName?.length ?? 0;
Exercise: Write a function formatScore that accepts number | null and returns a string. If null, return "-".
Lesson 3: Objects, arrays, and immutability patterns
Readonly helps express intent:
type Config = Readonly<{ apiBaseUrl: string; timeoutMs: number }>;
Spread and rest:
const defaults = { retries: 3, timeoutMs: 5000 };
const runtime = { ...defaults, timeoutMs: 8000 };
const { retries, ...rest } = runtime;
Destructuring with types:
function describe({ title, year }: { title: string; year: number }): string {
return `${title} (${year})`;
}
Exercise: Given const items = [{ id: 1 }, { id: 2 }], write a function that returns a new array with one more item { id: 3 } without mutating items.
Lesson 4: Unions, intersections, and literals
Literal types pin exact values:
type Theme = "light" | "dark";
Intersection combines shapes (use sparingly; prefer composition when it gets hard to read):
type Timestamps = { createdAt: string; updatedAt: string };
type Entity = { id: string } & Timestamps;
as const freezes literal types:
const routes = ["/health", "/users"] as const;
type Route = (typeof routes)[number];
Exercise: Model an API error as { code: "NOT_FOUND" } | { code: "RATE_LIMIT"; retryAfterSec: number }. Write a logError function that prints different messages for each case.
Lesson 5: Generics (the practical slice)
Generics let you reuse logic while keeping types precise.
function first<T>(items: T[]): T | undefined {
return items[0];
}
Constraint:
function getId<T extends { id: string }>(obj: T): string {
return obj.id;
}
Exercise: Implement groupBy that takes an array of { id: string; tag: string } and returns Record<string, string[]> mapping each tag to a list of ids.
Lesson 6: Classes, interfaces, and guards
Interfaces describe object shapes; classes are runtime values with types.
interface Logger {
info(message: string): void;
}
class ConsoleLogger implements Logger {
info(message: string): void {
console.log(message);
}
}
Type guards:
function isString(x: unknown): x is string {
return typeof x === "string";
}
Exercise: Create an HttpError class with statusCode and message. Write a function toUserMessage(err: unknown): string that returns a friendly string for HttpError and a generic fallback otherwise.
Self-check exercises
- Implement a tiny
Result<T, E>type and helpersok/errwithout usingany. - Parse JSON safely: write
parseJsonObject(input: string): Record<string, unknown> | nullthat returnsnullon invalid JSON or non-object roots. - Model pagination:
{ page: number; pageSize: number }and a functionoffsetthat returns SQL-style offset.
Next step
Continue to Phase B: Node & APIs after you can comfortably pass the exercises above without guessing types.