Before You Start
TypeScript is not a new language so much as a type system bolted onto one you may already know. These two lessons explain that relationship and get a compiler running.
What is TypeScript?
Microsoft's typed superset of JavaScript, led by C#'s own architect
TypeScript was created at Microsoft under Anders Hejlsberg — the architect of C# and, before that, Turbo Pascal and Delphi — and it is deliberately a *superset* of JavaScript: every valid JavaScript file is already valid TypeScript. Understanding that framing explains why TypeScript never changes runtime behaviour and why its types vanish entirely when compiled.
TypeScript's origin and design goals, the superset relationship with JavaScript, where it is used, and the Go-based compiler rewrite now underway.
Setting Up TypeScript
Install the compiler, then let tsconfig.json decide everything else
Installing TypeScript is one npm command; the part that actually matters is <code>tsconfig.json</code>, which decides how strictly your code is checked and what JavaScript it compiles down to. This lesson gets both in place so every later lesson can be typed and compiled, not just read.
Installing the compiler, initialising tsconfig.json, running tsc, and editor integration.
The Type System's Foundations
The annotations and built-in types you will use in nearly every file. Six lessons — resist the urge to annotate everything as you go; Lesson 4 explains why.
Basic Types
string, number, boolean — and the two types most languages don't have
TypeScript's primitives map straight onto JavaScript's, but two of its types are unusual: <code>unknown</code> (a value whose type you must narrow before using) and <code>never</code> (a value that can never exist, used for exhaustiveness checks). Both become important later, so meet them now.
Primitive types, arrays, objects, any vs unknown, never, void, and null/undefined.
Type Inference
TypeScript figures out most types on its own — annotations are the exception
Beginners over-annotate. TypeScript infers the type of nearly every variable, return value and callback parameter from context, and the inferred type is often *more* precise than what you would write by hand. Knowing where inference works is what keeps TypeScript code readable instead of noisy.
How inference works for variables, return types, and contextual typing of callbacks; plus where annotations are still worth writing.
Functions
Typed parameters, optional and default values, and overloads
Function signatures are where types pay off most immediately — a wrong argument is caught as you type rather than at runtime. This lesson also covers overloads: one function name with several valid signatures, which is common in library code you will read long before you write it.
Parameter and return type annotations, optional and default parameters, rest parameters, and function overloads.
Tuples
Fixed-length arrays with a type per position
A tuple is an array whose length and per-position types are both fixed — <code>[string, number]</code> rather than <code>(string | number)[]</code>. React's <code>useState</code> returns one, which is why destructuring it gives you correctly-typed values instead of a union.
Tuple syntax, named tuple elements, optional and rest elements, and readonly tuples.
Enums
Named constant groups — and why the handbook now suggests avoiding most of them
Enums are one of the few TypeScript features that emit real JavaScript rather than vanishing at compile time, which is exactly why the official handbook now recommends most new code use union types or <code>as const</code> objects instead. Worth learning because you will meet them in existing code.
Numeric and string enums, const enums, what they compile to, and the union-type/as-const alternatives now preferred.
Classes
public, private, protected and readonly — access control JavaScript never had
TypeScript adds compile-time access modifiers to JavaScript's classes, plus parameter properties that cut constructor boilerplate considerably. Note the distinction that trips people up: TypeScript's <code>private</code> is checked at compile time only, while JavaScript's own <code>#field</code> syntax is genuinely private at runtime.
Class syntax with types, access modifiers, readonly, parameter properties, abstract classes, and implementing interfaces.
Shapes, Unions & Narrowing — TypeScript's Defining Trick
This is the stage that makes TypeScript different from other typed languages: it understands ordinary JavaScript control flow well enough to narrow a union down to one member just from an `if` statement. Six lessons, and the heart of the course.
Interfaces vs Type Aliases
Two ways to describe an object shape, with one real structural difference
This is among the most-asked TypeScript questions, and the honest answer is that the two are nearly interchangeable for object shapes. The one real difference is declaration merging: interfaces can be reopened and extended by later declarations, type aliases cannot.
interface vs type syntax, extending each, declaration merging, and which to reach for by default.
Union & Intersection Types
| means “one of these”; & means “all of these at once”
A union type says a value is one of several types; an intersection says it satisfies all of them simultaneously. Unions are how TypeScript models the messy reality of JavaScript values, and they set up everything in the next three lessons — you cannot narrow what you have not first expressed as a union.
Union syntax, intersection syntax, literal-type unions, discriminated unions, and what you can access on an un-narrowed union.
Types & Interfaces, Tied Together
Structural typing, discriminated unions, and narrowing in one realistic model
This lesson is the synthesis for the shape half of this stage: seeing type aliases, interfaces, unions, discriminated unions and structural typing combined into one realistic data model, rather than each feature in isolation. Structural typing is the key idea to take away — TypeScript cares what a type's shape *is*, not what it is *named*.
Types, interfaces, type aliases, union types, discriminated unions, type narrowing, and structural typing together.
Strict Mode
strict: true is a bundle of flags — strictNullChecks is the one that matters most
<code>strict: true</code> switches on roughly a dozen individual checks at once. The most consequential by far is <code>strictNullChecks</code>, which stops <code>null</code> and <code>undefined</code> from being assignable to every type — turning JavaScript's most common crash into a compile error.
The individual flags strict bundles, strictNullChecks in detail, noImplicitAny, and migrating an existing codebase onto strict.
Type Narrowing
A runtime check inside an if statement is enough for TypeScript to know which type you have
This is TypeScript's signature move. Write <code>if (typeof x === "string")</code> and inside that block TypeScript *knows* x is a string — it reads your ordinary JavaScript control flow and narrows the type accordingly. No other mainstream type system tracks plain runtime checks this closely.
typeof and instanceof narrowing, truthiness checks, the `in` operator, discriminated-union narrowing, and exhaustiveness checking with never.
Type Guards
Teaching TypeScript to narrow a type you check yourself, with `value is Type`
When a check is too custom for TypeScript to follow on its own, a type guard — a function returning <code>value is Type</code> — tells the compiler what a <code>true</code> result proves. This is the escape hatch that keeps narrowing working at the edges of a real codebase, especially around data arriving from a network.
User-defined type guards, the `value is Type` return type, assertion functions, and validating external data safely.
Generics & Type-Level Programming
Where TypeScript goes further than most type systems: types that compute other types. Seven lessons. You will read code like this in every serious library long before you write it yourself, and that alone makes it worth learning.
Generic Constraints
extends restricts what a type parameter can be
An unconstrained type parameter tells TypeScript almost nothing — you cannot access any property on it, because it could be anything. <code>extends</code> is how you say “this can be any type, as long as it has at least this shape,” which is what makes generic code actually usable.
Type parameter syntax, extends constraints, keyof constraints, and default type arguments.
Conditional Types
An if/else for the type system: T extends U ? X : Y
A conditional type branches at the type level, letting one type definition produce different results depending on its input. With <code>infer</code>, it can also extract a type out of another type — the mechanism behind almost every clever type in the standard library.
Conditional type syntax, nested conditionals, the infer keyword, and distributive conditional types over unions.
Mapped Types
Derive a new type from every property of an existing one
A mapped type walks over the keys of an existing type and produces a new one, so <code>Readonly<T></code> or “make every field optional” becomes a single line instead of a duplicated interface that will drift out of sync.
Mapped type syntax, keyof and indexed access, adding/removing readonly and optional modifiers, and key remapping with `as`.
Template Literal Types
Build string literal types the way you'd build a template string
Template literal types let the type system construct and pattern-match string types — so <code>`on${Capitalize<K>}`</code> can generate <code>onClick</code>, <code>onChange</code> and so on from a set of keys. Unusual among type systems, and the reason some TypeScript APIs feel almost magical.
Template literal type syntax, combining with unions, inference from string patterns, and Uppercase/Lowercase/Capitalize.
Utility Types
Partial, Pick, Omit, Record — the standard library of the type system
TypeScript ships a set of ready-made mapped and conditional types that cover the transformations you need most: making fields optional, picking or omitting keys, building record types. This lesson also covers <code>satisfies</code> and <code>as const</code>, two modern additions that change how object literals get typed.
Partial, Required, Readonly, Pick, Omit, Record, Exclude/Extract, ReturnType, and the satisfies operator and as const.
Advanced Generics
Multiple type parameters, defaults, and generic classes
The patterns library authors rely on: several interacting type parameters, sensible defaults so callers rarely specify them, and generic classes that carry their type through every method. This is the point where reading a library's type definitions stops being intimidating.
Multiple type parameters, default type arguments, generic classes and interfaces, and generic constraints that reference each other.
Generics, Tied Together
Type parameters, keyof, conditional types, infer and mapped types in combination
This lesson is the synthesis for this stage: the individual type-level features you just met, combined the way real library code combines them — a generic function whose return type is computed from its input via a conditional type and a mapped type. Read it once the pieces are familiar, not before.
Type parameters, constraints, keyof, conditional types, infer, mapped types and template literal types in combination.
Compiler, Config & Modules
TypeScript's output and tooling are configuration-driven to an unusual degree, and most confusing TypeScript problems are really tsconfig problems. Five lessons on the layer around the code.
The TypeScript Compiler
tsc parses, checks, then erases your types
The single most clarifying fact about TypeScript: <code>tsc</code> type-checks your code and then deletes every type, emitting plain JavaScript. Nothing is checked at runtime. As of 2026 there is also a Go-based rewrite of the compiler that runs roughly ten times faster — a historic change for a project that had always been written in TypeScript itself.
The compile pipeline, type erasure, emitted output, declaration output, and the Go-based compiler rewrite and its performance impact.
Configuration
Path aliases, config inheritance, and the target/lib distinction
Most “TypeScript is broken” moments are really a <code>tsconfig.json</code> set up wrong. This lesson covers the options that actually matter day to day, including the <code>target</code> vs <code>lib</code> distinction most people conflate: one sets the JavaScript version you emit, the other declares which built-in APIs exist.
tsconfig.json structure, target vs lib vs module, path aliases and baseUrl, project references, and extends for shared config.
Modules & Namespaces
ES modules are the standard; namespaces are mostly legacy
TypeScript predates the ES module standard, so it invented namespaces to solve the same problem. Today <code>import</code>/<code>export</code> is simply the right answer, and namespaces mainly appear in older code and in declaration files. Knowing which one you are looking at saves real confusion.
import/export syntax, default vs named exports, `import type`, module resolution, and namespaces as a legacy feature.
Declaration Files
`.d.ts` files describe an untyped library's shape, with no implementation
A declaration file is types without code: it tells TypeScript the shape of a JavaScript library that has no types of its own. Understanding them explains where <code>@types/*</code> packages come from and how to type a dependency that ships none.
declare syntax, .d.ts structure, the @types ecosystem and DefinitelyTyped, ambient declarations, and module augmentation.
Testing TypeScript
Jest and Vitest run TypeScript directly — and type checking is a separate step
An important distinction: your test runner executing TypeScript is not the same as your types being checked. Most setups transpile tests without type-checking them for speed, which means <code>tsc --noEmit</code> belongs in your pipeline as its own step alongside the test run.
Setting up Vitest or Jest with TypeScript, typing mocks, `tsc --noEmit` as a distinct CI step, and testing types themselves.
Applied TypeScript
Three lessons on TypeScript where it is actually used: asynchronous code, React components, and decorators — the feature whose syntax changed entirely in 2023.
Typing Async Code
Promise<T> types what an async function eventually resolves to
An <code>async</code> function always returns a <code>Promise<T></code>, and <code>await</code> simply unwraps it back to <code>T</code>. The genuinely important part is typing failure: a rejected promise is typed as <code>unknown</code> in a catch block under modern settings, which forces you to narrow before assuming you caught an <code>Error</code>.
Promise<T>, async/await typing, typing rejected promises and catch clauses, Awaited<T>, and typing concurrent calls.
TypeScript with React
Typed props, typed hooks, and the .tsx extension
React is where most people meet TypeScript in earnest. Typed props turn a component's contract into something the compiler enforces, and typed hooks — especially <code>useState</code>, which returns a tuple — are where the tuple lesson from Stage 1 pays off directly.
The .tsx extension, typing props and children, typing useState/useRef/useReducer, event handler types, and generic components.
Decorators
The standard syntax replaced TypeScript's experimental one in 2023 — they are not compatible
Decorators are now a real JavaScript standard, and TypeScript's modern implementation follows it. Critically, the older <code>experimentalDecorators</code> version is a different, incompatible design — so a tutorial or library written for one will not work under the other. Knowing which you are looking at is the whole battle.
Standard decorator syntax and semantics, the legacy experimentalDecorators flag and how it differs, and common decorator use cases.
Concepts That Cross Languages
These pages explain ideas that are not specific to TypeScript — they apply to every language you will ever learn. Read them once and the next language costs you far less effort. They pair well with the lessons above rather than replacing them.
Build Real Things — 10 Projects
Reading is not enough. These 10 projects — five basic, five medium — are chosen because each one exercises something specific you learned above, and because they are genuinely idiomatic TypeScript rather than generic exercises. Each link below opens the project brief; the TypeScript implementation walkthrough is in progress.
Typed input parsing, loops, and narrowing a string to a number.
Typed functions, union types for the conversion direction, and exhaustiveness checking.
Reading files with typed Node APIs, and Record<string, number> for counting.
Interfaces for the task shape, and typing JSON parsed from disk as unknown first.
Typed configuration objects, as const character pools, and tuple returns.
fetch with typed responses, and validating external data before trusting its type.
Typed request/response handlers, discriminated-union error results, and shared types across the API boundary.
Discriminated unions for subcommands, and exhaustiveness checking so a missing case is a compile error.
A typed in-memory store with Map<string, string>, and branded types for codes vs URLs.
Typed parsing of semi-structured lines, and generics for a reusable top-N counter.
Practice & Experimentation
Ongoing, not a final step. Use these throughout the course — try every snippet you read, and run anything you are unsure about rather than assuming.
Run TypeScript code without installing anything, via the official TypeScript Playground.
Copy-ready idiomatic patterns to keep beside you while you build.
All 29 topic pages in one index, for looking things up later.