TypeScript Learning Roadmap: 20 Weeks from Everyday Types to a Full-Stack App

Follow a dependency-led TypeScript plan through BudgetLens, IssueBoard and TaskBoard, with exact weekly gates, strict checks and tested API boundaries.

KnowledgeGate Team

Exam prep & CS education

Updated 28 Sep 20265 min read

TypeScript syntax can feel familiar until JSON, DOM values, declarations and shared API contracts tempt you to reach for any. BudgetLens isolates types and runtime guards, IssueBoard adds generics and React, and TaskBoard carries a validated contract through a client and API. New to interfaces, unions, narrowing or generics? Start with TypeScript Tutorial for Beginners; otherwise, follow these project gates into full-stack integration. Use the programming and job-ready catalogue only when a prerequisite blocks progress. Stretch the calendar, but keep the dependency order.

1. TypeScript roadmap setup: fix nine weekly hours and test the JavaScript base

Plan 20 weeks x 9 hours = 180 hours. Use three 2-hour builds, each 20 minutes recall + 80 minutes coding + 20 minutes checking/tests, and a 3-hour weekend block: 60 minutes review + 90 minutes integration + 30 minutes planning or recovery.

Before week 1, write JavaScript that maps [{id:"A",minutes:45},{id:"B",minutes:60}] to IDs ["A","B"] and total 105. Reject null, a duplicate ID, and {id:"C",minutes:"30"} before summing. If object access, array methods or input checks require repeated lookup, take the Complete JavaScript Course before TypeScript.

Move one missed 2-hour block to the weekend. Two misses add a phase week.

Roadmap chart of seven TypeScript phases across 20 weeks, each showing its project and planned hours, plus the weekly cadence and gate.

2. Weeks 1-3: learn everyday TypeScript types through BudgetLens

Learn primitives, arrays, object types, literal unions, optional and readonly properties, plus typed functions. Build BudgetLens, a command-line summary. Model Transaction as a discriminated union: income rows have source, expense rows have category, and kind is "income" | "expense".

Seed tx-101, income, 50000, Salary; tx-102, expense, 12500, rent; tx-103, expense, 3200, food; and tx-104, expense, 1800, travel. Income is 50000. Expenses are 12500 + 3200 + 1800 = 17500. Balance is 50000 - 17500 = 32500. Reject amounts 0, -400 and NaN.

Compare inference with annotation, then remove inferable annotations. The earlier C# Learning Roadmap carries one StudyTracker through LINQ, async repositories and ASP.NET Core. BudgetLens, IssueBoard and TaskBoard instead separate TypeScript's compile-time model from runtime guards, React state and an Express boundary.

3. Weeks 4-6: replace any with narrowing at every BudgetLens boundary

Cover narrowing with typeof, in, equality, discriminated unions and type predicates, plus exhaustive never checks and safe error handling. Parsed JSON is unknown; annotations cannot validate it.

Reject {id:"tx-105", kind:"expense", amount:"900", category:"books"} because amount is a string, and {id:"tx-106", kind:"spend", amount:900, category:"books"} because kind is outside the union. Accept {id:"tx-105", kind:"expense", amount:900, category:"books"} only after every field passes the guard.

Expenses become 17500 + 900 = 18400, and balance becomes 50000 - 18400 = 31600. The gate is a parser returning a typed transaction or useful error, tests for all three payloads, and zero any in application code. Never mask a failed guard with as Transaction.

4. Weeks 7-9: use generics only where IssueBoard repeats a real pattern

Start IssueBoard with 101, Login loop, open, priority 2, estimate 90; 102, Slow dashboard, in-progress, priority 1, estimate 120; and 103, Typo in footer, closed, priority 3, estimate 45. Define Status = "open" | "in-progress" | "closed" and priority as 1 | 2 | 3.

Write groupBy<T, K>(items: readonly T[], keyOf: (item: T) => K): Map<K, T[]>. Grouping gives one item per status. Sorting on priority gives IDs [102, 101, 103]. Total estimate is 90 + 120 + 45 = 255 minutes.

Use Pick<Issue, "id" | "title" | "status"> for list rows and Partial<Pick<Issue, "status" | "priority">> for patches. Add a generic only when callers retain a useful type relationship.

5. Weeks 10-11: understand declarations, modules and the JavaScript runtime boundary

Split IssueBoard into domain, grouping, storage and UI modules. Learn named versus default exports, type-only imports, package type discovery and declaration files. Create legacy-priority.js, whose runtime function returns priority + (blocked ? 3 : 0).

Declare legacyPriority(priority: 1 | 2 | 3, blocked: boolean): number in legacy-priority.d.ts. The real outputs are (2, true) = 5 and (1, false) = 1. The wrong call legacyPriority("2", true) must fail type-checking. A declaration describes JavaScript; it changes no runtime and cannot prove the implementation honest.

Change the JavaScript fixture once so a runtime test contradicts the declaration. Finish with a non-circular module graph, an emitted declaration for public types, and legacy-bridge tests.

6. Weeks 12-15: turn on strict checks before integrating IssueBoard with React

Enable strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes, useUnknownInCatchVariables and noImplicitOverride. Then issues[0].title needs an absence check, {status: undefined} is invalid unless undefined is explicitly allowed, a caught value must be narrowed before reading .message, and a subclass method replacing a base method must carry override.

Build a React screen with a status filter, typed props, state and one form. Initially, status = "open" shows issue 101. Form strings are title = "Cache alert", priority = "2" and estimateMinutes = "75". Validate them before constructing issue 104 with numeric priority 2 and estimate 75.

After insertion, counts are open = 2, in-progress = 1, closed = 1; total estimate is 255 + 75 = 330 minutes. Treat fetched JSON as unknown before state. Keep domain calculations outside components. Finish only when tests reject priority 4, estimate 0 and blank title without an unsafe cast.

7. Weeks 16-19: carry one checked contract through a full-stack TaskBoard

Build TaskBoard with a React client, Node.js/Express API and shared types package. Shared TypeScript types do not replace runtime validation. Seed T-101, Type boundary, done, 90 minutes; T-102, React form, in-progress, 120; and T-103, API route, todo, 150. Initial total is 90 + 120 + 150 = 360, done is 90, and completion is 90 / 360 x 100 = 25%.

Implement GET /api/tasks?status=done, POST /api/tasks and GET /api/metrics. POST {title:"Contract test", estimateMinutes:"60", status:"done"} first. It must return 400 because the estimate is a string, with no row written. Then POST {title:"Contract test", estimateMinutes:60, status:"done"}. Return 201, ID T-104 and location /api/tasks/T-104.

After the valid write, total is 360 + 60 = 420; done is 90 + 60 = 150; completion is 150 / 420 x 100 = 35.714...%, displayed as 35.7%. Contract tests must assert the 400, 201 and metric transition before replacing the in-memory repository. Keep validation at the API boundary and persistence details behind an interface.

Flow diagram of one TaskBoard write: a string estimate is rejected with 400, a numeric 60 returns 201, and metrics move to 35.7% done.

8. Week 20: harden the three projects and choose the next full-stack step

Use 9 hours: 2 fixing unsafe assertions and strict errors, 3 on tests, 2 on setup notes, and 2 rebuilding from a clean checkout. Record each verified result in its README.

Before another framework, model a union, narrow unknown, preserve a generic relationship, test a declaration and trace a value through client, API and storage.

The MERN Stack course is an adjacent JavaScript full-stack route, not a TypeScript-specific course.

The short version

Keep the nine-hour rhythm. Move on only after each project type-checks, passes tests and runs cleanly. Next, add one scoped TaskBoard feature without weakening boundary validation.