Setup (Day 0 — 30–60 mins)
Goal
Get a clean TS sandbox you can reuse for the whole program.
Instructions
1. Create a folder:
○ ts-relearn/
2. Initialize:
○ npm init -y
○ npm i -D typescript ts-node @types/node eslint
@typescript-eslint/parser @typescript-eslint/eslint-plugin
3. Create [Link]:
○ npx tsc --init
4. Edit [Link] (baseline):
○ "target": "ES2022"
○ "module": "NodeNext"
○ "strict": true
○ "noUncheckedIndexedAccess": true
○ "exactOptionalPropertyTypes": true
○ "noImplicitOverride": true
○ "noFallthroughCasesInSwitch": true
Activity (deliverable)
● Create src/[Link] that prints something and run it:
○ npx ts-node src/[Link]
Program structure
4 weeks, 5 days/week, 60–90 mins/day
Each week ends with a small project that forces you to use what you learned.
Week 1 — Type System Fundamentals
(rebuild instincts)
Day 1: Types, inference, narrowing
Content
● unknown vs any
● Type inference
● Narrowing: typeof, in, truthy checks, discriminated unions (intro)
Activities
1. Write a function parseInput(input: unknown):
○ If string that looks like a number → return number
○ If number → return as-is
○ Else throw an error
Create a union:
type ApiResult = { ok: true; data: string } | { ok: false; error: string }
2. Implement handleResult(r: ApiResult): string.
Deliverable
● src/[Link] with both functions + 10 sample calls.
Day 2: Arrays, tuples, enums (and better alternatives)
Content
● readonly arrays, tuples
● Prefer string unions over enums most of the time
● Literal types and as const
Activities
Build a const object map:
const RideStatus = { Idle: "idle", OnTrip: "on_trip", Done: "done" } as const;
type RideStatus = typeof RideStatus[keyof typeof RideStatus];
1.
2. Create setStatus(status: RideStatus) and demonstrate invalid values fail
compile-time.
3. Write a tuple-returning function splitName(full: string): readonly [first:
string, last: string].
Deliverable
● src/[Link]
Day 3: Functions, overloads, generics (basic)
Content
● Function types
● Optional params vs default params
● Overloads (when useful)
● Generics: T, constraints T extends ...
Activities
1. Write a generic first<T>(items: readonly T[]): T | undefined
2. Write an overloaded format:
○ format(n: number): string → 1,234.00
○ format(s: string): string → trimmed + collapsed spaces
3. Add tests by running code (just console asserts).
Deliverable
● src/[Link]
Day 4: Objects, structural typing, excess property checks
Content
● Interfaces vs types
● Excess property checks (object literals)
● Index signatures
● Record<K, V>
Activities
Model:
type User = { id: string; name: string; meta?: Record<string, string> };
1.
2. Implement mergeMeta(user: User, patch: Record<string, string>):
User
3. Demonstrate excess property check:
○ Show object literal error and fix via variable or exact typing.
Deliverable
● src/[Link]
Day 5 Project: “Typed Config Loader”
Goal
Load a JSON-like config object safely and validate shape with TS narrowing (no external libs).
Requirements
● AppConfig:
○ env: "dev" | "uat" | "prod"
○ retries: number (0–10)
○ baseUrl: string (must start with http)
● Function: loadConfig(input: unknown): AppConfig (throws with helpful
message)
● Provide src/[Link] with sample good/bad inputs.
Week 2 — Intermediate TS (the stuff that
makes TS powerful)
Day 6: Advanced unions, discriminated unions (deep)
Content
● Discriminated unions patterns
● Exhaustiveness checking with never
Activities
Define:
type Payment =
| { kind: "cash"; amount: number }
| { kind: "card"; amount: number; last4: string }
| { kind: "wallet"; amount: number; provider: "gcash" | "maya" };
1.
2. Write describePayment(p: Payment): string
Add an exhaustiveness check:
const _exhaustive: never = p;
3.
Day 7: Utility types (Pick/Partial/Omit/Required/Readonly)
Content
● Utility types + real use-cases
● “Patch” update modeling
Activities
1. Create type UserPatch = Partial<Omit<User, "id">>
2. Write applyPatch(user: User, patch: UserPatch): User
3. Enforce runtime rules:
○ name cannot be empty
○ meta keys max length 20
Day 8: Type guards & assertion functions
Content
● Custom type guards x is T
● Assertion functions asserts x is T
Activities
Implement:
function isNonEmptyString(x: unknown): x is string
function assertNonEmptyString(x: unknown, msg?: string): asserts x is string
1.
2. Use them in loadConfig-style validation.
Day 9: Generics with constraints + key inference
Content
● keyof, indexed access types
● Generic helpers like pluck, get
Activities
Implement:
function get<T, K extends keyof T>(obj: T, key: K): T[K]
1.
2. Implement pluck<T, K extends keyof T>(items: T[], key: K):
Array<T[K]>
Day 10 Project: “Typed Event Bus”
Goal
A tiny in-memory event emitter with typed event names + payloads.
Requirements
Define:
type Events = {
"[Link]": { message: string };
"[Link]": { message: string; code?: string };
"[Link]": { id: string; name: string };
};
●
● on(eventName, handler)
● emit(eventName, payload)
● Ensure compile-time safety:
○ wrong payload type should fail
○ unknown event name should fail
Week 3 — Practical Patterns (real-world
TS)
Day 11: Working with async + Promise typing
Content
● Promise<T>
● Error handling patterns
● Result<T, E> style
Activities
Implement:
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };
1.
2. Write safe<T>(fn: () => Promise<T>): Promise<Result<T, unknown>>
Day 12: Zod-like thinking without a library (schemas by
hand)
Content
● Runtime validation + TS types
● Keeping validation close to boundaries (IO)
Activities
● Write validators:
○ isNumber, isString, isRecord
● Compose them to validate nested objects.
Day 13: API client typing (DTO vs domain models)
Content
● DTO types vs domain types
● Mapping layer
Activities
Define DTO:
type UserDTO = { id: string; full_name: string };
type User = { id: string; name: string };
1.
2. Write toUser(dto: UserDTO): User and toUserDTO(user: User): UserDTO
Day 14: TS + testing mindset (without heavy frameworks)
Content
● Using assert (Node)
● Table-driven tests
Activities
● Create src/[Link] with:
○ assertEqual(actual, expected, message?)
○ test(name, fn)
● Write tests for your Week 2 event bus.
Day 15 Project: “Mini HTTP Router Types”
Goal
Type-safe route definitions.
Requirements
Define routes as:
const routes = {
"/users/:id": (params: { id: string }) => "ok",
"/health": (_: {}) => "ok",
} as const;
●
● Create a match(path: string) that returns:
○ matched handler
○ extracted params
● Params must be typed correctly for each route.
(Doesn’t need to be perfect; focus on typing strategy.)
Week 4 — Mastery Moves (the TS you use
in serious codebases)
Day 16: Conditional types
Content
● T extends U ? X : Y
● Using conditional types in helpers
Activities
Create:
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
●
● Demonstrate with 5 examples.
Day 17: Mapped types & template literal types
Content
● Mapped types: [K in keyof T]
● Template literal types: ${string}.${string}
Activities
● Create a type-safe “dot path” for a simple object:
○ type Paths<T> = ...
● Use it in a getPath(obj, path) function (even partial support is fine).
Day 18: satisfies and keeping literals without losing
type safety
Content
● as const vs satisfies
● Preventing widening
Activities
Build a configuration object using satisfies:
const cfg = {
env: "dev",
retries: 3,
} satisfies { env: "dev" | "uat" | "prod"; retries: number };
●
Day 19: TS for library design (public API hygiene)
Content
● Exported types vs internal types
● Avoiding “leaky” any
● Using generics to keep API flexible
Activities
● Refactor one of your projects (Event Bus or Router):
○ separate internal types
○ export clean type surfaces
○ add JSDoc comments
Day 20 Capstone Project: “Type-Safe Workflow Engine”
Goal
A tiny workflow runner where each step has typed input/output.
Requirements
A step:
type Step<I, O> = { name: string; run(input: I): Promise<O> };
●
● Build pipe(a, b, c...) that ensures step outputs match next inputs.
● Provide 1 workflow:
○ input { userId: string }
○ fetch user → validate → produce summary string
● Add a Result type for failures.
Daily routine (use this every day)
1. Read/skim content (10–15 mins)
2. Type first, then run: write types before implementation (15–20 mins)
3. Compile often: npx tsc -p . --noEmit (every few minutes)
4. Refactor pass (10 mins): rename, extract helpers, tighten types
5. Notes (5 mins): what TS feature tripped you today?
Checkpoints (how you know it’s working)
By the end, you should be comfortable with:
● unknown + custom type guards instead of any
● discriminated unions + exhaustive checks
● generics with keyof patterns
● conditional/mapped/template-literal types (at least basic)
● designing “typed APIs” (Event Bus, Router, Pipeline)
●