# TypeScript From Zero

> Learn TypeScript from nothing to genuinely advanced: install it and the basics - types, functions, interfaces, unions, generics, classes, and the build - then the deep half: the structural type system, utility and mapped types, conditional and template-literal types, and typing the real world. Mental-model-first, with clear explanations.


---

# TypeScript From Zero

TypeScript is the language most professional JavaScript is written in now. It's not a different
language you have to relearn - it's JavaScript with a type checker bolted on top. You write code that
looks almost exactly like the JS you know, add a few annotations about what your data *is*, and a
checker catches whole categories of bugs in your editor *before the code ever runs*. The "undefined is
not a function" and "I passed the wrong shape" mistakes stop being 2am production incidents and become
red squiggles you fix as you type.

This guide takes you the whole way: from "I've never run `tsc`" to understanding what the type system is
*actually doing* underneath - including the genuinely advanced features (mapped types, conditional
types) that power the libraries you use. We go mental-model-first the whole way: before any annotation,
you'll understand what it means and why TypeScript made the choice it did.

> 📝 This guide assumes you know **JavaScript** - values, functions, objects, `async`/`await`. If you
> don't yet, do [JavaScript From Zero](/guides/javascript-from-zero) first (its final phase hands off
> directly to this one). TypeScript only makes sense once the JavaScript underneath does.

It's one zero-to-hero journey in two halves. **Phases 1–8 are the basics** - enough to type real
applications confidently. **Phases 9–12 are the deep half** - the structural type system, utility and
mapped types, conditional and template-literal types, and typing messy real-world data, the stuff that
separates "writes TypeScript" from "understands the type system." Each phase carries a difficulty badge
so you can see the climb.

## How to read this

- **New to TypeScript (but know JS)?** Read 1–8 in order - each builds on the last. Type the examples
  yourself; watching the checker catch a mistake teaches more than reading about it. Come back for 9+
  when the basics feel comfortable.
- **Already using TypeScript day to day?** Jump to the deep half - [Phase 9: The Type System,
  Deep](09-the-type-system-deep.md) onward is where TypeScript stops being "JS with annotations" and
  becomes a system you can compute *with*.

## The phases

**Part 1 - The basics (🟢 Basic → 🟡 Intermediate)**
1. **[Install & Your First Program](01-install-and-first-program.md)** 🟢 - `tsc`, a `tsconfig.json`, and compiling TS to JS.
2. **[Why Types & the Basic Types](02-why-types-and-basic-types.md)** 🟢 - what the checker buys you; `string`/`number`/`boolean`/arrays/tuples, `any` vs `unknown`, inference.
3. **[Functions & Annotations](03-functions-and-annotations.md)** 🟢 - parameter and return types, optional and default params, `void`.
4. **[Objects, Interfaces & Type Aliases](04-objects-interfaces-and-types.md)** 🟢 - shape types, `interface` vs `type`, optional and `readonly` properties.
5. **[Unions, Literals & Narrowing](05-unions-and-narrowing.md)** 🟡 - union types, literal types, type guards, and discriminated unions.
6. **[Generics](06-generics.md)** 🟡 - type parameters, constraints, and writing code that works over many types safely.
7. **[Classes & OOP in TypeScript](07-classes-and-oop.md)** 🟡 - access modifiers, `implements`, `abstract`, and parameter properties.
8. **[Modules, tsconfig & the Build](08-modules-and-tsconfig.md)** 🟡 - ES modules, `strict` mode, and the compiler options that actually matter.

**Part 2 - Beyond the basics (🔴 Advanced)**
9. **[The Type System, Deep](09-the-type-system-deep.md)** 🔴 - structural typing, widening and narrowing, and how inference really works.
10. **[Utility & Mapped Types](10-utility-and-mapped-types.md)** 🔴 - `Partial`/`Pick`/`Omit`/`Record`, `keyof`, and writing your own mapped types.
11. **[Conditional & Template Literal Types](11-conditional-and-template-types.md)** 🔴 - `T extends U ? X : Y`, `infer`, and types built from string patterns.
12. **[Typing the Real World](12-typing-the-real-world.md)** 🔴 - third-party `@types`, declaration files, typing `fetch`/JSON, and taming `any`.

**Finale**
13. **[Where to Go Next](13-where-to-go-next.md)** 🟢 - React, Node, full-stack with TypeScript, and what to build.

> Frameworks (React, Next, NestJS) are their own guides - they *use* TypeScript heavily, but this guide
> makes the *type system itself* make sense, top to bottom.


---

# Install & Your First Program - tsc, tsconfig, and the Compile Step

If you're arriving from the JavaScript guide, you already know the punchline: TypeScript is JavaScript plus a type checker that reads your code *without running it* and points at mistakes while you type. This phase gets it onto your machine and running.

## The mental model: TypeScript compiles to JavaScript

**Nothing runs TypeScript directly.** Not Node, not your browser, not anything. TypeScript is a language for *you and the checker* to talk in. Before your code can execute, a tool reads your `.ts` files, verifies the types line up, and emits ordinary `.js` files - and those are what actually run.

That tool is `tsc`.

📝 **`tsc`** - the TypeScript compiler. It does two jobs at once: **type-checks** your code (the part that catches bugs) and **transpiles** it - strips the type annotations, rewrites any newer syntax - producing plain JavaScript. "Compile" here mostly means "check, then translate down to JS."

The pipeline: you write `.ts`, `tsc` turns it into `.js`, then a normal JavaScript runtime - Node or a browser - runs the `.js`.

```mermaid
flowchart LR
  TS["hello.ts<br/>(you write this)"] --> TSC["tsc<br/>check + translate"]
  TSC --> JS["hello.js<br/>(plain JavaScript)"]
  JS --> RUN["node / browser<br/>(runs it)"]
```

*What just happened:* the diagram traces the only path your code can take. The `.ts` file never reaches a runtime; `tsc` sits in the middle as a gate, so type errors surface *here*, before anything runs. What comes out the other side is `.js` with every annotation deleted, so a browser or Node can run it without knowing TypeScript exists.

💡 **Why this matters from day one.** Types live at *compile time* - they help you and `tsc`, then they're erased. The `.js` that runs has no idea what a type is. Keep this in your back pocket; it explains half the surprises beginners hit.

## Install TypeScript

You need Node installed already (the JavaScript guide covers that - confirm with `node --version`). TypeScript ships as an npm package, two ways to install it.

**The quick way - install it globally.** This puts the `tsc` command on your whole system so you can run it from any folder:

```bash
npm install -g typescript
```

**The way real projects do it - install it as a dev dependency.** Inside a project folder, this pins a specific TypeScript version to *that project*, so everyone working on it (and your CI server) uses the exact same compiler:

```bash
npm install --save-dev typescript
```

⚠️ **A global install is convenient but lies to you about versions.** Install only globally and every project on your machine shares one `tsc` - the day two projects need different versions, you're stuck. Real projects keep TypeScript in `devDependencies` and run it via `npx tsc` (uses the project's local copy). For *learning* on a scratch file, global is fine; for anything you'll commit, prefer local. We'll use a global `tsc` below to keep commands short.

Confirm it landed by asking the compiler its version:

```bash
tsc --version
```
```console
Version 5.8.3
```

*What just happened:* `tsc --version` reported the installed compiler version. Your number will differ - what matters is getting `Version` and digits back instead of `command not found`. (Installed locally instead? The bare `tsc` command won't be found; use `npx tsc --version` instead.)

## Your first program

Create a file called `hello.ts` - the `.ts` extension is what tells `tsc` "this is TypeScript." Put a small typed function in it:

```typescript
function greet(name: string): string {
  return `Hello, ${name}!`;
}

console.log(greet("TypeScript"));
```

*What just happened:* plain JavaScript with two annotations bolted on. `name: string` says the parameter must be a string; `: string` after the parentheses says the function returns one. Everything else is JavaScript you already know - the annotations are notes to the checker, with no effect on what the program *does*.

Now compile it. Hand the file to `tsc`:

```bash
tsc hello.ts
```

Silence means success. But look in your folder and you'll find a new file next to `hello.ts`:

```typescript
// hello.js  - emitted by tsc
"use strict";
function greet(name) {
    return `Hello, ${name}!`;
}
console.log(greet("TypeScript"));
```

*What just happened:* `tsc` checked your types, found no problems, and wrote `hello.js` - the same code with `: string` and the return annotation **stripped away** (plus a `"use strict";` header `tsc` adds on top). The types did their job at check time, then disappeared, leaving plain JavaScript. That emitted `.js` is what you actually run.

Run it with Node, like any JavaScript file:

```bash
node hello.js
```
```console
Hello, TypeScript!
```

*What just happened:* Node ran the *generated* `hello.js`, not your `hello.ts`. Node has never heard of TypeScript - by the time it runs, all it sees is ordinary JavaScript. That's the full loop you'll repeat: **edit `.ts` → `tsc` → run the `.js` with Node.**

## Watch the checker catch a bug

You could've written `hello.js` by hand and skipped TypeScript entirely so far. Here's the actual payoff. Break the program on purpose: pass a number where `greet` demands a string.

```typescript
function greet(name: string): string {
  return `Hello, ${name}!`;
}

console.log(greet(42)); // 42 is a number, not a string
```

Now run `tsc hello.ts` again:

```bash
tsc hello.ts
```
```console
hello.ts:5:19 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

5 console.log(greet(42)); // 42 is a number, not a string
                    ~~

Found 1 error in hello.ts:5
```

*What just happened:* `tsc` refused to wave the code through. It pointed at the exact file, line, and column (`hello.ts:5:19`), named the problem - a `number` can't go where a `string` is required - and underlined the offending `42`. Crucially, this happened **before the program ran**: no execution, no `NaN` quietly flowing downstream, no mysterious failure three screens later.

💡 **This is the entire pitch for TypeScript, in one error message.** In plain JavaScript, `greet(42)` runs without complaint and produces `"Hello, 42!"` - maybe harmless, maybe a disaster when the value matters. TypeScript moves that discovery from "sometime at runtime, if you're lucky" to "right now, in your editor." (In a real editor like VS Code, you don't even run `tsc` - the same red underline appears as you type.)

## tsconfig.json and a smoother workflow

Typing `tsc hello.ts` for one file is fine, but real projects have dozens of files and compiler options you don't want to retype every time. Generate a config file:

```bash
tsc --init
```

*What just happened:* `tsc --init` created a `tsconfig.json` - a short, commented starter file preset with sensible defaults. Now you can run plain `tsc` (no filename), and the compiler finds every `.ts` file in the project and compiles them per the rules in that file. The config *is* your project's compile recipe.

📝 **`tsconfig.json`** - the configuration file for `tsc`. It defines which files to compile, what JavaScript version to emit, where output goes, and - most importantly - how strict the type checking is. Running `tsc` in a folder that has one means "compile this whole project, my way."

One option deserves a flag now even though we cover it later: `strict`. A freshly-generated `tsconfig.json` turns it on (`"strict": true`) - leave it on.

💡 **Leave `strict` on, always.** It switches on the checks that catch the most bugs, including forcing you to handle `null` and `undefined` instead of letting them slip through. Beginners are sometimes tempted to turn it off because it complains more - that's exactly backwards; the complaints are the value. [Phase 8](08-modules-and-tsconfig.md) covers `tsconfig` and strict mode in depth.

Two more things make day-to-day work far less tedious.

**Watch mode** recompiles automatically on every save, so you're not re-running `tsc` by hand:

```bash
tsc --watch
```

*What just happened:* `tsc --watch` (or `tsc -w`) starts the compiler and leaves it running: compiles once, then watches your files, re-checking and re-emitting the instant you save and printing errors in real time. Edit, glance at the terminal, know immediately whether the checker is happy. Leave it running in a spare terminal while you work.

**`ts-node`** runs a `.ts` file directly during development, skipping the separate compile-then-run dance:

```bash
npx ts-node hello.ts
```
```console
Hello, TypeScript!
```

*What just happened:* `ts-node` compiled `hello.ts` **in memory** and ran the result in one step - no `hello.js` written to disk. A convenience for quick experiments: one command instead of two. It still runs the exact `.ts → tsc → .js → run` pipeline under the hood, just with the middle steps hidden. For shipping real code, compile properly with `tsc`.

## Recap

1. **Nothing runs TypeScript directly.** `tsc` checks your types and emits plain `.js` - that's what Node or the browser runs: the `.ts → tsc → .js → run` pipeline.
2. **Install it** with `npm install -g typescript` (quick) or `npm install --save-dev typescript` (how real projects pin a version); confirm with `tsc --version`.
3. **The loop is edit → compile → run:** write `hello.ts`, run `tsc hello.ts` to produce `hello.js` with annotations stripped, then `node hello.js`.
4. **The payoff is compile-time errors.** A number where a string is required makes `tsc` report the exact file, line, and reason *before the program runs* - the whole reason TypeScript exists.
5. **`tsconfig.json`** (from `tsc --init`) is your project's compile recipe; keep `"strict": true` on. Phase 8 goes deep on it.
6. **`tsc --watch`** recompiles on every save, and **`ts-node`** runs a `.ts` file directly during development - both conveniences over the same pipeline.

You can now write, compile, and run TypeScript, and you've seen the checker catch a bug. Next: *why* types pay off, and the basic types you'll annotate every day.

## Quick check

Lock in the one idea that drives the whole workflow - what actually runs, and when bugs get caught:

```quiz
[
  {
    "q": "When you run `node hello.js` after compiling `hello.ts`, what is Node actually executing?",
    "choices": [
      "The plain JavaScript that `tsc` emitted, with all type annotations stripped out",
      "The `hello.ts` file directly - Node understands TypeScript natively",
      "The type annotations, which Node checks again at runtime",
      "Both files at once, merged together by `tsc`"
    ],
    "answer": 0,
    "explain": "Nothing runs TypeScript directly. `tsc` translates `hello.ts` into plain `hello.js`, deleting every type annotation. Node runs that emitted JavaScript and never needs to know TypeScript exists."
  },
  {
    "q": "You change a call to `greet(42)` when `greet` expects a `string`. When do you find out it's wrong?",
    "choices": [
      "At compile time - `tsc` reports the error before the program ever runs",
      "At runtime, when Node crashes on the line",
      "Never - TypeScript allows it and silently converts the number",
      "Only if you remember to add a runtime check yourself"
    ],
    "answer": 0,
    "explain": "That's the entire point of TypeScript. `tsc` checks types without running the code, so it flags `greet(42)` at compile time - naming the file, line, and reason - long before any runtime is involved."
  },
  {
    "q": "What does running `tsc --init` give you, and why keep `\"strict\": true`?",
    "choices": [
      "A `tsconfig.json` recipe for the whole project; strict mode turns on the checks that catch the most bugs",
      "A compiled `hello.js`; strict mode makes that file run faster",
      "A global install of TypeScript; strict mode disables type checking for speed",
      "A new `.ts` file template; strict mode is only for advanced users and should be off"
    ],
    "answer": 0,
    "explain": "`tsc --init` creates `tsconfig.json`, the config that lets you run plain `tsc` over a whole project. `strict: true` enables the strongest, most bug-catching checks - leave it on; the extra complaints are exactly the value."
  }
]
```


---

# Why Types & the Basic Types - What the Checker Buys You

In Phase 1 you ran your first program. So far types feel like extra typing for no obvious reward. Here's where the reward shows up.

**A type checker is a second pair of eyes that reads your code without running it.** Plain JavaScript only notices a mistake the moment the broken line executes - possibly after corrupting a cart total or a database row. TypeScript notices it the instant you type it, red underline in your editor. Same code, same logic, but the discovery moves from "three screens later, confused" to "right now, while it's cheap."

## Why types - a bug that hides until it's expensive

Here's the payoff, made concrete with a bug you've almost certainly shipped: a function reads a field off an object, but the object spells it differently. In JavaScript, reading a nonexistent property isn't an error - it quietly returns `undefined`.

```javascript runnable
function priceWithTax(item, rate) {
  return item.price + item.price * rate;
}

const product = { name: "Notebook", cost: 12 }; // oops: "cost", not "price"

console.log(priceWithTax(product, 0.2));
```
```console
NaN
```
*What just happened:* `product` has a `cost` field, but `priceWithTax` reads `item.price`. That returns `undefined`, and `undefined + undefined * 0.2` evaluates to `NaN`, returned without complaint. The program keeps running - that `NaN` flows downstream into a total, a chart, a saved record, breaking something far from the actual typo.

⚠️ **The dangerous part isn't the crash - it's the *lack* of one.** A crash at least names a line; a silent `NaN` travels far from its source before causing visible damage, which is why these bugs eat afternoons.

The same function in TypeScript, with the shape spelled out:

```typescript
interface Product {
  name: string;
  price: number;
}

function priceWithTax(item: Product, rate: number): number {
  return item.price + item.price * rate;
}

const product = { name: "Notebook", cost: 12 };
console.log(priceWithTax(product, 0.2)); // error flagged here
```
```console
Argument of type '{ name: string; cost: number; }' is not assignable to parameter of type 'Product'.
  Property 'price' is missing in type '{ name: string; cost: number; }' but required in type 'Product'.
```
*What just happened:* `interface Product` declares the shape an item must have. Pass `{ name, cost }` and the checker compares it against `Product`, sees the required `price` is missing, and reports the error **in your editor, before you run anything** - the exact bug from the runnable demo above, caught by a tool that never executed your code.

## The basic types

Most values are one of three primitives, and TypeScript's names for them are the words you'd expect.

- `string` - text: `"Ada"`, `"hello"`.
- `number` - any number, integer or float (no separate `int`/`double`): `42`, `3.14`, `-7`.
- `boolean` - `true` or `false`.

Attach a type to a variable with a colon after the name - a **type annotation**.

```typescript
let name: string = "Ada";
let age: number = 36;
let isAdmin: boolean = false;

name = 42; // error
```
```console
Type 'number' is not assignable to type 'string'.
```
*What just happened:* `let name: string` tells the checker "this slot holds text, forever." The first three lines fit their declared types and pass silently. Put a `number` into the `string` slot and the checker objects - that promise is what catches `42` before it sneaks in and surprises you later.

## Inference - you annotate less than you think

What surprises people coming from other typed languages: you rarely write those annotations. TypeScript reads the value right of the `=` and figures out the type for you - **type inference** - which means the annotations above were redundant.

💡 **TypeScript infers a variable's type from its initializer.** `let count = 0` is already typed `number` - the annotation `let count: number = 0` adds nothing the checker didn't already know.

```typescript
let count = 0;        // inferred as number
let label = "ready";  // inferred as string
let done = false;     // inferred as boolean

count = "zero"; // error - count is number, inferred from 0
```
```console
Type 'string' is not assignable to type 'number'.
```
*What just happened:* You wrote no types at all, yet `count` is fully a `number` - assigning `"zero"` is still caught. TypeScript saw `0`, concluded "number," and enforced it from then on: safety without the noise. The practical rule: **annotate the boundaries, not every variable.** Function parameters and return types are worth spelling out (the checker can't guess what a caller will pass); locals almost never need one - let inference do it.

## Arrays and tuples

A list of values gets a type too - for an array where every element is the same type, write the element type followed by `[]`.

```typescript
let scores: number[] = [90, 85, 100];
let names: string[] = ["Ada", "Linus"];

scores.push(95);   // fine
scores.push("A+"); // error - only numbers allowed
```
```console
Argument of type 'string' is not assignable to parameter of type 'number'.
```
*What just happened:* `number[]` means "an array of numbers." Every operation on it - `push`, indexing, iteration - is checked against that element type, so a `string` slipping into a number array is caught. `Array<number>` means the same thing; `number[]` is the common spelling.

Sometimes you want a fixed-length sequence where *each position* has its own type - a pair, a coordinate, a row. That's a **tuple**, the types listed in order inside brackets.

```typescript
let point: [number, number] = [10, 20];
let entry: [string, number] = ["age", 36];

entry = [36, "age"]; // error - wrong types in wrong positions
```
```console
Type 'number' is not assignable to type 'string'.
Type 'string' is not assignable to type 'number'.
```
*What just happened:* `[string, number]` says "exactly two elements: a string then a number." Unlike `string[]` (any number of strings), a tuple pins down both length and type per slot, and flipping the order gets caught position by position. Reach for tuples when the shape is genuinely fixed - a key/value pair, an `(x, y)` point - and an array for a homogeneous list of unknown length.

## `any` vs `unknown` - the escape hatch and the safe one

Eventually you'll hit a value whose type you don't know yet - API data, a `JSON.parse` result, something dynamic. TypeScript gives you two types for "I don't know what this is," and they behave differently.

📝 **`any`** - turns type checking *off* for that value. Call it, index it, add to it - the checker stays silent. An escape hatch out of the type system.

📝 **`unknown`** - the safe "could be anything" type. It can *hold* any value, but the checker won't let you *use* it until you've proven what it is (a "narrowing" check). The top type, with the guardrails left on.

`any` is a hole in the floor; `unknown` is a locked door that asks for the key.

```typescript
let a: any = "hello";
a.toFixed(2);     // no error - any disables checking (this crashes at runtime!)

let u: unknown = "hello";
u.toFixed(2);     // error - must narrow first
```
```console
'u' is of type 'unknown'.
```
*What just happened:* `a` is `any`, so `.toFixed(2)` (a number method) on a string sails through the checker - then explodes at runtime, exactly the bug types should prevent. `u` is `unknown`, so the same misuse is *blocked at compile time*: the checker won't let you touch it until you've established what it is. With `unknown`, you'd first check `typeof u === "number"` inside an `if`, then call number methods - the checker walks you to safety instead of looking away.

⚠️ **`any` defeats the entire purpose of TypeScript.** Every `any` is a spot where the checker has been told to stop looking, so bugs flow straight through. It's tempting when the checker is nagging, but you're not fixing the problem - you're hiding it. Treat `any` as a last resort.

💡 **When a value's type is genuinely unknown, reach for `unknown`, not `any`.** Both say "I don't know what this is yet" - but `unknown` forces you to find out before you use it, while `any` lets you pretend you already know. The first surfaces bugs; the second buries them.

## Recap

1. **Types pay off by catching bugs at edit-time** - the silent `NaN` from a misspelled field gets a red underline before the code ever runs, instead of corrupting data three screens away.
2. The **basic types** are `string`, `number`, and `boolean`; attach one with an annotation like `let name: string = "Ada"`.
3. **Inference means you annotate less than you think** - `let count = 0` is already `number`. Annotate function boundaries; let inference handle locals.
4. **Arrays** use `number[]` (or `Array<number>`) for a homogeneous list; **tuples** use `[string, number]` for a fixed-length, fixed-type-per-position sequence.
5. ⚠️ **`any` switches off type checking** for a value - an escape hatch that defeats the point and lets bugs through. Avoid it.
6. **`unknown` is the safe top type**: it holds anything but forces you to narrow before use. Prefer it whenever a value's type is genuinely not known yet.

You can now read and write the everyday types that make up most TypeScript code. Next: **functions** - annotating parameters and return types, and how much inference still does for you.

## Quick check

Lock in the three ideas that matter most here - when types catch bugs, how much inference does for you, and why `unknown` beats `any`:

```quiz
[
  {
    "q": "In TypeScript, when would the misspelled-field bug (`item.price` vs an object with `cost`) be caught?",
    "choices": [
      "At edit-time in your editor, before the code ever runs",
      "At runtime, when the line executes and returns NaN",
      "Never - TypeScript ignores property names",
      "Only after you deploy and a user reports it"
    ],
    "answer": 0,
    "explain": "The checker compares the passed object against the declared `Product` type without running anything, sees `price` is missing, and underlines the error in your editor. That edit-time catch is the core payoff of types."
  },
  {
    "q": "Given `let count = 0;` with no annotation, what type does `count` have?",
    "choices": [
      "number - TypeScript infers it from the initializer 0",
      "any - because you didn't annotate it",
      "It has no type until you add `: number`",
      "unknown - until you narrow it"
    ],
    "answer": 0,
    "explain": "TypeScript reads the value on the right of `=` and infers the type. `0` is a number, so `count` is `number` - assigning a string to it later is still caught. You rarely need to annotate locals."
  },
  {
    "q": "Why is `unknown` safer than `any` for a value whose type you don't know yet?",
    "choices": [
      "`unknown` blocks you from using the value until you narrow it; `any` turns checking off entirely",
      "`unknown` is faster at runtime than `any`",
      "`any` cannot hold strings, but `unknown` can",
      "There is no real difference - they behave identically"
    ],
    "answer": 0,
    "explain": "`any` disables type checking, so misuse (like calling a number method on a string) slips through and crashes at runtime. `unknown` holds anything but forces a narrowing check before use, so the checker keeps protecting you."
  }
]
```


---

# Functions & Annotations - Typing the Boundaries

In Phase 2 you typed individual variables. Useful, but bugs live at the *seams* - the moment one piece of code hands data to another and assumes it's the right shape. A function call is exactly that handoff: caller passes arguments in, function passes a result back. Get the shapes wrong and you're back to the silent `undefined`/`NaN` failures types exist to kill.

The mental model: **a function signature is a contract.** It states, in a form the checker enforces, "give me these types and I'll give you that type back." Both sides are held to it - the caller can't pass garbage, and the function can't return the wrong thing. 💡 **Annotate the boundaries, let inference handle the inside.** The parameters and return type are worth writing down; local variables, TypeScript usually figures out on its own. That habit gives you most of the safety for very little typing.

## Parameter and return types - the contract itself

The most basic annotated function: types on each parameter, and a type after the parameter list for what comes back.

```typescript
function add(a: number, b: number): number {
  return a + b;
}

add(2, 3);      // 5 - fine
add(2, "3");    // Error flagged here
```

*What just happened:* The signature `(a: number, b: number): number` is the contract. The first call satisfies it; the second passes a string where a `number` is required, so the checker rejects it before the code runs:

```console
Argument of type 'string' is not assignable to parameter of type 'number'.
```

That's the caller side. The function side is enforced too - `: number` after the parentheses promises the *return* value is a number, and TypeScript holds you to it.

```typescript
function add(a: number, b: number): number {
  return `${a + b}`;   // Error flagged here
}
```

*What just happened:* The body builds a *string* with the template literal, but the signature promised a `number` - the checker catches the broken promise at the `return`:

```console
Type 'string' is not assignable to type 'number'.
```

Here's what surprises people: **you can often leave the return type off entirely.** TypeScript reads the body and *infers* it.

```typescript
function add(a: number, b: number) {
  return a + b;   // TS infers the return type is number
}

const result = add(2, 3);   // result is typed as number, automatically
```

*What just happened:* With no `: number` written, TypeScript looked at `a + b` and concluded the function returns `number`. `result` gets that type with zero annotation - inference doing the "inside" work for you.

So why write the return type by hand? Because **an explicit return type locks the contract.** Writing `: number` tells the checker "this function must return a number" - a future edit that accidentally returns a string gets flagged at *this* function. Without it, the wrong type flows out silently and the error surfaces wherever some caller chokes on it, far from the cause.

💡 **When to write the return type:** on anything public or important - exported functions, anything other people call. For small local helpers, letting inference do it is fine. Parameters, by contrast, are almost always worth annotating: TypeScript can rarely infer what a parameter *should* be.

## Arrow functions - same contract, different syntax

Everything above applies unchanged to arrow functions: types on parameters, return type after the parameter list.

```typescript
const add = (a: number, b: number): number => a + b;

const double = (n: number) => n * 2;   // return type inferred as number
```

*What just happened:* `add` spells out its return type; `double` lets inference handle it. Same rules as `function` declarations - the arrow is just a different way to write the same contract.

A second, distinct skill: typing a variable that *holds* a function. The type of a function value is written `(params) => returnType` - looks like an arrow function but describes a *type*, not running anything.

```typescript
let op: (x: number, y: number) => number;

op = (a, b) => a + b;        // fine - matches the signature
op = (a, b) => `${a}${b}`;   // Error flagged here
```

*What just happened:* `op` is declared to hold "a function taking two numbers and returning a number." The first assignment matches - notice `a` and `b` need no annotation, since TypeScript already knows from `op`'s type what they must be (**contextual typing**). The second assignment returns a string, breaking the contract:

```console
Type '(a: number, b: number) => string' is not assignable to type '(x: number, y: number) => number'.
```

⚠️ **Don't confuse the two arrows.** `(x: number) => number` as a *type* (after a colon, in an annotation) describes a function's shape. `(x) => x * 2` as a *value* is an actual arrow function. Same symbol, opposite roles - one is a label, the other the thing being labeled. You'll use the type form constantly once you start typing callbacks.

## Optional and default parameters

Real functions don't always take every argument. TypeScript has two distinct tools for that, and the difference matters.

An **optional parameter** is marked with `?` - the caller may skip it, and its value is then `undefined`.

```typescript
function greet(name: string, title?: string): string {
  if (title) {
    return `Hello, ${title} ${name}`;
  }
  return `Hello, ${name}`;
}

greet("Ada");              // "Hello, Ada"
greet("Ada", "Dr.");       // "Hello, Dr. Ada"
```

*What just happened:* The `?` on `title` makes it optional, so `greet("Ada")` is legal. Inside the function, `title` might be a string *or* `undefined` - hence the `if (title)` check before using it.

That's the crux. 📝 **An optional parameter's type secretly includes `undefined`.** `title?: string` is really `title: string | undefined`, and the checker stops you from treating it as a guaranteed string.

```typescript
function shout(message?: string): string {
  return message.toUpperCase();   // Error flagged here
}
```

```console
'message' is possibly 'undefined'.
```

*What just happened:* Because `message` is optional, it might be `undefined`, and `undefined.toUpperCase()` would crash at runtime. TypeScript catches it now and forces you to handle the missing case (an `if`, a default, or `?.`).

A **default parameter** is different: give it a fallback value with `=`, so it's *never* `undefined` inside the function.

```typescript
function greet(name: string, greeting: string = "Hello"): string {
  return `${greeting}, ${name}`;
}

greet("Ada");             // "Hello, Ada"
greet("Ada", "Welcome");  // "Welcome, Ada"
```

*What just happened:* When the caller omits `greeting`, it falls back to `"Hello"`. Inside the body, `greeting` is a plain `string` - not `string | undefined` - because the default guarantees a value is always there. TypeScript even infers the parameter's type *from* the default, so you can often drop `: string` and write `greeting = "Hello"`.

⚠️ **Optional vs. default - the type difference is the whole point.** An optional param (`x?: T`) hands you `T | undefined` and makes you deal with the gap. A default param (`x: T = value`) fills the gap for you and hands you a clean `T`. Reach for a default when you have a sensible fallback; reach for optional when "absent" is a case you want to handle differently.

## Rest parameters - "however many"

To accept any number of trailing arguments, collect them with `...` into an array - the type is the array type, `number[]` for a list of numbers.

```typescript
function sum(...nums: number[]): number {
  return nums.reduce((total, n) => total + n, 0);
}

sum(1, 2, 3);        // 6
sum(10, 20);         // 30
sum();               // 0
sum(1, "2");         // Error flagged here
```

*What just happened:* `...nums: number[]` gathers every argument into an array called `nums`, and the checker enforces that each one is a number - so `sum(1, "2")` is rejected. Inside, `nums` is an ordinary `number[]`, so `.reduce` and other array methods are fully typed. One signature, any arity, full type safety.

## `void` and `never` - functions that don't (usefully) return

Not every function hands back a value.

📝 **`void`** - the return type of a function that doesn't return a meaningful value. It runs for its *effect* (printing, saving, updating) rather than to produce a result you'd use.

```typescript
function logMessage(text: string): void {
  console.log(`[log] ${text}`);
  // no return statement - or a bare `return;`
}

const ignored = logMessage("hi");   // ignored is typed as void
```

*What just happened:* `logMessage` does its job - printing - and returns nothing. `void` documents that. Capture its result and you get a `void` value, which TypeScript won't let you do anything useful with - correctly signalling "there's nothing here to use."

A rarer, sharper cousin: 📝 **`never`** - the type of a function that *never returns at all*: it always throws or loops forever. Not "returns nothing" (that's `void`) but "control flow never reaches the end."

```typescript
function fail(reason: string): never {
  throw new Error(reason);   // always throws - never returns
}

function loopForever(): never {
  while (true) {
    // runs until the process is killed
  }
}
```

*What just happened:* `fail` always throws, so execution never makes it past the `throw` - `never` says exactly that. `loopForever` never exits its loop, same idea.

The distinction: a `void` function *finishes and comes back* having produced no value; a `never` function *never comes back at all*. You'll mostly *write* `void` (event handlers, loggers, savers) and mostly *encounter* `never` rather than write it - TypeScript uses it internally, and it becomes genuinely useful later for exhaustiveness checking on unions.

## Recap

1. **A function signature is a contract.** Annotate parameters and the return type, and TypeScript holds both the caller and the function body to it - wrong-typed arguments and returns are caught before the code runs.
2. **Return types are often inferred** from the body, so you can omit them on small helpers. Write them explicitly on important/exported functions to *lock* the contract, so a mistake points at the function rather than a distant caller.
3. **Arrow functions take the same annotations**, and the function-type form `(x: number) => number` types a variable that holds a function - distinct from an arrow function *value*.
4. **Optional `x?: T` includes `undefined`** in its type (you must handle the missing case); **default `x: T = value` does not** (the fallback guarantees a real value).
5. **Rest parameters `...nums: number[]`** collect any number of trailing arguments into a typed array, giving variable arity with full safety.
6. **`void`** types a function that returns no meaningful value; **`never`** types one that never returns at all (always throws or loops forever) - finishing-with-nothing versus never-finishing.

You can now type the contracts between the parts of your program - the highest-leverage place types pay off. Next, from functions to *data*: describing object shapes with interfaces and type aliases.

## Quick check

Lock in the three ideas that bite hardest - return inference, the optional-vs-default type difference, and void vs. never:

```quiz
[
  {
    "q": "You write `function add(a: number, b: number) { return a + b; }` with no return type annotation. What type does TypeScript give the return value?",
    "choices": [
      "`number` - TypeScript infers it from the body (`a + b` is two numbers added)",
      "`any` - without an explicit annotation the return type is untyped",
      "`void` - a function with no return annotation returns nothing",
      "It's a compile error; the return type is required"
    ],
    "answer": 0,
    "explain": "TypeScript reads the body and infers the return type. Since `a + b` adds two numbers, it concludes the function returns `number`. Writing `: number` explicitly is optional here - it's worth doing on important/exported functions to lock the contract, but inference handles small helpers fine."
  },
  {
    "q": "What's the difference between `function f(x?: string)` and `function f(x: string = \"hi\")` inside the function body?",
    "choices": [
      "With `x?`, `x` is typed `string | undefined` and you must handle the missing case; with the default, `x` is always a plain `string`",
      "There is no difference - both make the parameter optional in the same way",
      "With `x?`, `x` is always a `string`; with the default, `x` might be `undefined`",
      "The default version makes `x` required, while `x?` makes it optional"
    ],
    "answer": 0,
    "explain": "An optional parameter's type secretly includes `undefined` (`x?: string` means `string | undefined`), so the checker forces you to handle the absent case. A default parameter fills the gap with a fallback, so inside the body it's a guaranteed `string` - no undefined to worry about."
  },
  {
    "q": "When should a function's return type be `never` rather than `void`?",
    "choices": [
      "When the function never returns at all - it always throws or loops forever",
      "When the function returns nothing but finishes normally, like a logger",
      "Whenever the function has no `return` statement",
      "When the function returns `undefined` explicitly"
    ],
    "answer": 0,
    "explain": "`void` means the function finishes and comes back having produced no useful value (a logger, a saver). `never` means control flow never reaches the end - the function always throws or loops forever, so it can't return anything. Finishing-with-nothing versus never-finishing."
  }
]
```


---

# Objects, Interfaces & Type Aliases - Describing Shapes

So far you've typed individual values - a `string` here, a `number` there. But look at the code you write all day: almost none of it is loose values. It's *objects* - a user, a product, a request, a config, bundles of related fields traveling together. This is where typing starts genuinely paying off, because TypeScript lets you describe the *shape* of those bundles once and reuse it everywhere.

**A type for an object is a contract about its shape** - which fields exist, and what type each is. Write it down once, give it a name, and the checker enforces it everywhere that shape appears. Misspell a field, forget a required one, pass the wrong kind of value - caught in the editor, before anything runs.

## Inline object types - fine for one-offs, tiring at scale

The most direct way to type an object is to write its shape right where you need it, in `{ ... }` braces.

```typescript
function greet(user: { name: string; age: number }): string {
  return `Hi ${user.name}, you are ${user.age}`;
}

greet({ name: "Ada", age: 36 }); // ok
```

*What just happened:* `{ name: string; age: number }` is the object's shape spelled out in place - `user` must have a `name` string and an `age` number. The field separator inside the braces can be a semicolon or comma; semicolons are the convention.

This is reasonable for a shape used in one spot. The trouble starts the moment a second function needs the *same* shape:

```typescript
function greet(user: { name: string; age: number }): string {
  return `Hi ${user.name}`;
}

function canVote(user: { name: string; age: number }): boolean {
  return user.age >= 18;
}
```

*What just happened:* The exact same shape is written twice. If `user` later grows an `email` field, you have to hunt down and update *every* inline copy - miss one and they silently disagree about what a "user" is. Repetition like this signals the shape deserves a name.

## `interface` - give a shape a name

The cleanest way to name an object shape is an **interface**.

📝 **Interface** - a named description of an object's shape: its properties and each one's type. Declare it once with `interface Name { ... }`, then use `Name` anywhere you'd otherwise spell the shape out inline.

```typescript
interface User {
  name: string;
  age: number;
}

function greet(user: User): string {
  return `Hi ${user.name}`;
}

function canVote(user: User): boolean {
  return user.age >= 18;
}

greet({ name: "Ada", age: 36 }); // ok
```

*What just happened:* `interface User` defines the shape once. Both functions annotate their parameter as `User` instead of repeating the braces - "what a user is" lives in exactly one place. An interface generates no JavaScript; like all types, it's erased at compile time and exists purely to guide the checker.

Now watch the contract work. Leave out a required field and the checker stops you cold:

```typescript
interface User {
  name: string;
  age: number;
}

const u: User = { name: "Ada" }; // missing age
```
```console
Property 'age' is missing in type '{ name: string; }' but required in type 'User'.
```

*What just happened:* `User` says every user has both `name` *and* `age`. `{ name: "Ada" }` has no `age`, so the assignment violates the contract, and TypeScript points at the exact missing property - in your editor, before you run a thing. The omission surfaces at edit-time, not as a confusing `undefined` three screens away.

## `type` aliases - name *anything*, not only objects

There's a second way to name a shape: a **type alias**.

📝 **Type alias** - the `type` keyword binds a name to *any* type expression: an object shape, but also a union, a primitive, a tuple, or a function type. `type ID = string | number` names something an interface can't.

For an object, a `type` alias looks almost identical to an interface.

```typescript
type User = {
  name: string;
  age: number;
};

function greet(user: User): string {
  return `Hi ${user.name}`;
}
```

*What just happened:* `type User = { ... }` names the same shape an interface would - note the `=` sign and trailing semicolon, which `type` uses and `interface` doesn't. As a parameter annotation, it behaves exactly like the interface version.

The difference shows when naming something that *isn't* an object. An interface can only describe an object shape; a `type` alias can name a union, a primitive, or a tuple.

```typescript
type ID = string | number;        // a union - interface can't do this
type Pair = [number, number];     // a tuple
type Name = string;               // an alias for a primitive

const a: ID = "abc123";
const b: ID = 42;
const point: Pair = [3, 4];
```

*What just happened:* `ID` names "a string *or* a number" - a union type (more on those next phase). `Pair` names a two-element tuple; `Name` is a readable alias for `string`. None are object shapes, so none could be an `interface`. This breadth is the type alias's superpower: it names *any* type, not only objects.

## `interface` vs `type` - the plain guidance

The straight answer to the question everyone trips on: **for object shapes, `interface` and `type` are mostly interchangeable.** Both name a shape, both get enforced identically, both support optional and readonly fields and extension. You can write almost any real codebase using only one.

The differences are real but narrow:

- **`interface` can be re-opened (declaration merging).** Declare `interface User` twice and TypeScript *merges* them into one. A `type` alias can't be redeclared - a second `type User` is an error. Merging is mostly for augmenting library types; it's why interfaces are conventional for *public* object APIs others might extend.
- **`type` can express things an interface can't** - unions, tuples, primitives, and (later) intersections and mapped types.

💡 **Rule of thumb.** Reach for **`interface` when describing an object shape** - it's the convention, error messages read a touch nicer, and it leaves the door open for merging. Reach for **`type` when you need a union, a tuple, or anything an interface can't express.** Don't agonize: pick `interface` for objects and move on. The compiler tells you when the other form is needed by rejecting what it can't do.

## Optional, `readonly`, and extending

Three small tools turn basic shapes into the ones you'll actually write: optional fields, read-only fields, and building one shape on another.

**Optional properties with `?`.** Putting `?` after a field's name makes it optional - present or absent - and code reading it must account for it possibly being `undefined`.

```typescript
interface User {
  name: string;
  email?: string; // optional - may or may not be there
}

const a: User = { name: "Ada" };                       // ok, no email
const b: User = { name: "Grace", email: "g@xyz.io" };  // ok, with email
```

*What just happened:* The `?` on `email` makes it optional, so both objects satisfy `User` - one with an email, one without. The type of `email` is effectively `string | undefined`, so using it, the checker will nudge you to handle the missing case. Optional is for fields that genuinely might not be there, not a license to skip required data.

**Read-only fields with `readonly`.** Prefix a field with `readonly` and TypeScript forbids reassigning it after creation - for values set once and never changed, like an `id` or a creation timestamp.

```typescript
interface User {
  readonly id: number;
  name: string;
}

const u: User = { id: 1, name: "Ada" };
u.name = "Ada L.";  // ok - name is writable
u.id = 2;           // not allowed
```
```console
Cannot assign to 'id' because it is a read-only property.
```

*What just happened:* You set `id` once when the object was built. Reassigning `name` is fine, but changing `id` is rejected at compile time. ⚠️ `readonly` is a *compile-time* guarantee only - erased before the code runs, so it stops *you* in the editor but does nothing to a value at runtime. It enforces intent during development; it is not a runtime lock.

**Extending a shape.** Real shapes build on each other - an `Admin` is a `User` with extra fields. Interfaces use `extends`; type aliases use an intersection `&`. Both produce "everything from the base, plus the new fields."

```typescript
interface User {
  name: string;
  age: number;
}

interface Admin extends User {
  role: "admin";   // plus everything from User
}

const root: Admin = { name: "Ada", age: 36, role: "admin" };

// The type-alias equivalent, using intersection:
type AdminAlias = User & { role: "admin" };
```

*What just happened:* `interface Admin extends User` means an `Admin` has `name`, `age`, *and* `role` - the object must supply all three. `type AdminAlias = User & { role: "admin" }` does the same with an intersection (`&`), combining shapes into one with every member of both. Pick `extends` in interfaces and `&` in type aliases; the resulting contract is equivalent.

## Recap

1. **Inline object types** (`{ name: string; age: number }`) work for a one-off, but repeating the same shape across functions is a maintenance trap - the signal to name it.
2. An **`interface`** names an object's shape once and reuses it everywhere; the checker enforces the contract, flagging a missing required field right in the editor.
3. A **`type` alias** also names object shapes, but goes further: it can name **unions, tuples, and primitives** - things an interface can't express.
4. For object shapes the two are **mostly interchangeable**. Rule of thumb: **`interface` for object shapes, `type` when you need a union or something an interface can't do.**
5. **`?`** makes a field optional (possibly `undefined`); **`readonly`** forbids reassignment after creation. ⚠️ It's a compile-time check, erased at runtime - not a runtime lock.
6. Build shapes on each other with **`interface Admin extends User`** or the type-alias equivalent **`User & { ... }`** - both mean "everything from the base, plus more."

You can now describe the shape of real data and reuse it. Next, the other half: values that can be *one of several* things - a status that's `"loading"` or `"done"`, an id that's a string *or* a number - and how TypeScript narrows them safely.

## Quick check

Lock in the three decisions you'll make constantly - interface vs type, optional vs required, and what `readonly` actually guarantees:

```quiz
[
  {
    "q": "You need to name a type that is `string | number`. Which tool can express it?",
    "choices": [
      "A `type` alias - `type ID = string | number`",
      "An `interface` - `interface ID { string | number }`",
      "Either one; interfaces support unions too",
      "Neither; unions can't be named in TypeScript"
    ],
    "answer": 0,
    "explain": "An interface can only describe an object shape. A union like `string | number` isn't an object, so only a `type` alias can name it. This is the main reason to reach for `type` over `interface`."
  },
  {
    "q": "Given `interface User { name: string; email?: string }`, which object is valid?",
    "choices": [
      "`{ name: \"Ada\" }` - `email` is optional, so it can be omitted",
      "`{ email: \"a@x.io\" }` - only one field is needed",
      "`{}` - every field is optional once any field is",
      "Only `{ name: \"Ada\", email: \"a@x.io\" }` - both fields are required"
    ],
    "answer": 0,
    "explain": "The `?` on `email` makes it optional, so it may be absent. But `name` has no `?`, so it's still required - an object must include `name` and may or may not include `email`."
  },
  {
    "q": "What does `readonly id: number` actually guarantee?",
    "choices": [
      "The compiler rejects reassigning `id` after creation, but it's erased at runtime - not a runtime lock",
      "The value of `id` is frozen at runtime and any reassignment throws an error",
      "`id` can never be set at all, not even when the object is created",
      "Other fields on the object also become read-only automatically"
    ],
    "answer": 0,
    "explain": "`readonly` is a compile-time guarantee: TypeScript flags an attempt to reassign `id` in your editor. Like all types it's erased before the code runs, so it enforces intent during development but does nothing at runtime."
  }
]
```


---

# Unions, Literals & Narrowing - One of Several Shapes

So far your types have described one thing: a value is a `string`, or a `Product`, or a `number[]`. But real programs are full of values that are *one of several possibilities* - an API field that's a string or `null`, a status that's `"pending"`, `"shipped"`, or `"cancelled"` and nothing else, a shape that's either a circle or a square, each with its own measurements.

**TypeScript lets you say "this value is one of these specific possibilities" - and then refuses to let you use it until you've figured out which one you're holding.** That second half is the magic. The checker tracks your code branch by branch, and inside an `if` that proves "this is a string," it *knows* it's a string and lets you call string methods. This is where TypeScript stops feeling like paperwork and starts feeling genuinely smart.

## Union types - a value that's one of several types

📝 **Union type** - a type written `A | B` meaning "a value of type `A` *or* type `B`." A `string | number` variable can hold either, and TypeScript only lets you do things valid for *both* until you prove which one you've got.

That last clause is the rule that trips people up.

```typescript
function format(id: string | number) {
  return id.toUpperCase(); // Error
}
```
```console
Property 'toUpperCase' does not exist on type 'string | number'.
  Property 'toUpperCase' does not exist on type 'number'.
```

*What just happened:* `id` is `string | number`, so it might genuinely be a number, and numbers have no `.toUpperCase()`. TypeScript blocks the call because the operation isn't safe for *every* member of the union - you're only allowed what's common to all until you narrow (the next big idea). This feels annoying for a day, then you realize it's stopping a real crash: `.toUpperCase()` on a number throws at runtime.

## Literal types - exact values, not just "some string"

A union of *types* is useful. A union of *exact values* is where it gets powerful.

📝 **Literal type** - a type that is one specific value, not a whole category. `"shipped"` is a type only the string `"shipped"` satisfies; `42` is a type only the number `42` satisfies. Combine them with `|` to describe a fixed set of allowed values.

Compare a bare `string` against a literal union for a fixed set of choices.

```typescript
// Bare string: any string is accepted, including typos.
function setAlignLoose(value: string) { /* ... */ }
setAlignLoose("centre"); // accepted - but "centre" is a typo, no warning

// Literal union: only these three are allowed.
type Align = "left" | "right" | "center";
function setAlign(value: Align) { /* ... */ }

setAlign("center"); // fine
setAlign("centre"); // Error
```
```console
Argument of type '"centre"' is not assignable to parameter of type 'Align'.
```

*What just happened:* With a bare `string` parameter, every string is fair game - the misspelled `"centre"` sails through and breaks something later. With `type Align = "left" | "right" | "center"`, the checker rejects anything outside the set, *and* your editor autocompletes the three valid options. For any fixed set of choices - alignments, HTTP methods, status codes, sizes - a literal union beats a bare `string` every time.

💡 **Why this is a big deal.** A literal union turns "a value that's supposed to be one of these" (enforced by hope and comments) into "a value the compiler *guarantees* is one of these." Typos become compile errors, and you never write a `default` branch handling an impossible string.

## Narrowing - proving which member you have

You saw the union problem: you can't use `string`-only operations on a `string | number`. The fix is **narrowing**, the heart of working with unions.

📝 **Narrowing** - writing a runtime check that lets TypeScript shrink a value's type within a branch of code. Inside an `if` that proves the value is a string, the checker *narrows* the type from `string | number` to just `string`, and every string operation is allowed.

You don't tell the checker the narrowed type - it reads your ordinary runtime check and works it out. The everyday tools:

- **`typeof x === "string"`** - for primitives (`"string"`, `"number"`, `"boolean"`, etc.).
- **`"key" in obj`** - checks whether a property exists, narrowing to the variant that has it.
- **`Array.isArray(x)`** - separates an array from a non-array member.
- **A truthiness check** like `if (x)` - narrows `string | null` to `string` by ruling out `null`/`undefined`.

`typeof` narrowing fixing the broken `format` from earlier:

```typescript
function format(id: string | number): string {
  if (typeof id === "string") {
    // In here, TypeScript knows id is a string.
    return id.toUpperCase();
  }
  // Down here, the string case is ruled out - id is a number.
  return id.toFixed(2);
}

console.log(format("abc")); // "ABC"
console.log(format(3.14159)); // "3.14"
```

*What just happened:* `typeof id === "string"` does double duty. At runtime it picks the right branch; at *compile* time TypeScript uses it to narrow `id` to `string` inside the `if`, so `.toUpperCase()` is allowed. After the `if`, the only remaining possibility is `number`, so `.toFixed(2)` needs no extra check. The checker followed your logic like a careful reader would, and the earlier error is gone.

## Discriminated unions - modeling "one of N variants"

Narrowing a `string | number` is handy. The real power move is modeling *objects* that come in several shapes, and TypeScript has a pattern built for it.

📝 **Discriminated union** - a union of object types sharing a common literal field (the *discriminant* or *tag*), with a different value per variant. Checking that tag narrows the value to one specific shape, with all its fields available. Also called a *tagged union*.

A circle has a radius; a square has a side length. Give each a `kind` tag, union them, and a `switch` on `kind` narrows each case to its full shape.

```typescript
interface Circle {
  kind: "circle";
  radius: number;
}
interface Square {
  kind: "square";
  side: number;
}
type Shape = Circle | Square;

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      // Tag is "circle" → shape is a Circle here, so .radius exists.
      return Math.PI * shape.radius ** 2;
    case "square":
      // Tag is "square" → shape is a Square here, so .side exists.
      return shape.side ** 2;
  }
}

console.log(area({ kind: "circle", radius: 2 })); // 12.566...
console.log(area({ kind: "square", side: 3 })); // 9
```

*What just happened:* `Shape` is a union of two object types, each carrying a literal `kind`. Inside `case "circle"`, TypeScript narrows `shape` to `Circle`, so `shape.radius` is valid and `shape.side` would be an error. The `kind` field unlocks the right shape in each branch - no guessing or casting.

💡 **This is the answer to "how do I model one of N variants?"** Discriminated unions are TypeScript's idiomatic replacement for enums-with-data, sealed classes, or a bag of optional fields. Each variant declares exactly the fields it has - no nullable `radius` that's only sometimes meaningful, no runtime confusion about which fields are valid. The shape *is* the documentation, and the checker enforces it.

## Exhaustiveness checking - the compiler catches what you forgot

Discriminated unions have one more trick, and it's what makes the pattern indispensable on a real codebase: the compiler can force you to handle *every* variant, forever.

Suppose someone adds a `Triangle` to `Shape` next month but forgets to update `area`. Without protection, `area` silently returns `undefined` for triangles - a classic bug that hides until production. The fix: a `default` case assigning the value to a variable of type `never`.

📝 **`never`** - the type of a value that can't exist. If every variant has been handled, the value left in `default` has type `never`. Assigning anything *other* than `never` to a `never` variable is a compile error - exactly the alarm you want.

```typescript
interface Circle { kind: "circle"; radius: number; }
interface Square { kind: "square"; side: number; }
interface Triangle { kind: "triangle"; base: number; height: number; } // newly added
type Shape = Circle | Square | Triangle;

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.side ** 2;
    default:
      // We forgot the "triangle" case, so shape is Triangle here - not never.
      const _exhaustive: never = shape; // Error
      return _exhaustive;
  }
}
```
```console
Type 'Triangle' is not assignable to type 'never'.
```

*What just happened:* Because `"triangle"` isn't handled by any `case`, a `Triangle` value reaches `default`. We try to assign it to `_exhaustive: never` - but a `Triangle` is very much *something*, not `never`, so the checker errors, pointing at the exact function you forgot to update. Add `case "triangle"` and the leftover type becomes `never` again, and the error vanishes.

⚠️ **This is the killer feature - don't skip the `never` line.** Without it, adding a variant fails *silently*: `area` just returns `undefined` for the new shape. With it, every switch over the union lights up red the instant you extend it, handing you a checklist of exactly what to fix. On a large codebase, this turns a terrifying refactor into a mechanical one.

## Recap

1. **Union types** (`A | B`) describe a value that's one of several types. You can only use operations valid for *every* member until you narrow.
2. **Literal types** (`"left" | "right" | "center"`) describe a fixed set of exact values - beating a bare `string` with autocomplete and typo-rejection.
3. **Narrowing** is the heart of unions: a runtime check (`typeof`, `in`, `Array.isArray`, truthiness) lets TypeScript shrink the type inside that branch, so you can safely use member-specific operations.
4. **Discriminated unions** add a shared literal tag (`kind`) to each variant; switching on the tag narrows to that variant's full shape - TypeScript's answer to enums-with-data.
5. ⚠️ **Exhaustiveness checking** with `never` in the `default` case makes the compiler *error* when you add a variant and forget to handle it - turning silent `undefined` bugs into a precise compile-time checklist.

## Quick check

Lock in the three ideas that do the real work - why unions block operations, how narrowing fixes that, and what `never` buys you:

```quiz
[
  {
    "q": "Why does calling `id.toUpperCase()` on a parameter typed `string | number` produce a compile error?",
    "choices": [
      "Until you narrow, you can only use operations valid for every member of the union - and `number` has no `.toUpperCase()`",
      "`toUpperCase` is deprecated in TypeScript and must be replaced with `toUppercase`",
      "Union types can never have methods called on them at all",
      "TypeScript requires you to cast every union to `any` before using it"
    ],
    "answer": 0,
    "explain": "A `string | number` value might be a number at runtime, and numbers have no `.toUpperCase()`. TypeScript only allows operations common to all members until a check (like `typeof id === \"string\"`) narrows the type to one that supports the call."
  },
  {
    "q": "In a discriminated union `type Shape = Circle | Square`, what makes a `switch (shape.kind)` able to access `shape.radius` inside `case \"circle\"`?",
    "choices": [
      "The shared literal `kind` tag lets TypeScript narrow `shape` to `Circle` in that branch, exposing its specific fields",
      "TypeScript guesses the shape based on which fields you try to access",
      "All fields of every variant are always available on every branch",
      "You must manually cast `shape` to `Circle` with `as` in each case"
    ],
    "answer": 0,
    "explain": "The literal `kind` field is the discriminant. Matching `case \"circle\"` proves `shape.kind` is `\"circle\"`, so TypeScript narrows `shape` to the `Circle` variant - making `radius` available and `side` an error, with no cast needed."
  },
  {
    "q": "What does assigning the leftover value to `const _exhaustive: never = shape;` in the `default` case accomplish?",
    "choices": [
      "If a new variant is added and left unhandled, it reaches `default` as a real type (not `never`), so the assignment errors and points you at the gap",
      "It makes the switch run faster by skipping the default branch at runtime",
      "It converts the union into an enum automatically",
      "It silences all type errors in the function"
    ],
    "answer": 0,
    "explain": "When every variant is handled, the value in `default` has type `never` and the assignment is legal. Add an unhandled variant and that value becomes a real type - not assignable to `never` - so the compiler errors at exactly the spot you forgot to update. That's exhaustiveness checking."
  }
]
```


---

# Generics - Reusable Code That Keeps Its Types

Here's a tension you've already felt. You write a small helper - grab the first element of an array, wrap a value in a box, swap two things - and want it to work for *any* type: numbers, strings, users, whatever. The lazy way to say "any type" is the `any` type. But `any` doesn't mean "any type, tracked" - it means "stop checking entirely." The moment a value passes through `any`, TypeScript forgets what it was. Your reusable helper becomes a black hole that swallows every type it touches.

**A generic is a placeholder for a type, filled in at the moment you call the code.** Instead of committing to `number` or `string` when you *write* the function, you leave a blank, and TypeScript fills it with the real type when someone *uses* it. One function, many types, and the types survive the trip - that's the difference between `any` (types thrown away) and generics (types carried through).

## The problem: `any` throws the types away

Write the simplest reusable function and watch it fail. `first` returns the first element of an array. We don't know what's in it, so we reach for `any`.

```typescript
function first(arr: any[]): any {
  return arr[0];
}

const n = first([1, 2, 3]);   // n is `any`
const s = first(["a", "b"]);  // s is `any`

n.toUpperCase();  // no error?! n is a number - this crashes at runtime
```

*What just happened:* The function works at runtime - it really does return the first element. But look at the *types*: `first([1, 2, 3])` should give a `number`, `first(["a", "b"])` a `string`. Instead both come back as `any`, because the return type is annotated `any` - we told TypeScript "I don't know what this is," and it took us at our word and stopped checking. So `n.toUpperCase()` - a string method on a number - sails past the type checker and blows up only when the code runs. We threw away exactly the safety we adopted TypeScript for.

⚠️ **`any` is contagious.** A single `any` in a return type spreads to every variable that touches the result, and each of *those* stops being checked too. Reusable helpers are the worst place for `any` precisely because they're called everywhere - one can quietly switch off type-checking across half your codebase.

## Type parameters: the blank you fill in at the call site

What we actually want: "this function works for *some* type `T`, and whatever `T` goes in, that's what comes out." TypeScript lets you write exactly that with a **type parameter**.

📝 **Type parameter** - a named placeholder for a type, in angle brackets after the function name (`<T>`). It stands in for a real type supplied - usually *inferred* - when the function is called. By convention a single capital letter (`T` for "type", `K` for "key", `V` for "value"), but any name works.

`first` again, done right:

```typescript
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

const n = first([1, 2, 3]);     // n is `number | undefined`
const s = first(["a", "b"]);    // s is `string | undefined`
const u = first<boolean>([]);   // u is `boolean | undefined`
```

*What just happened:* `<T>` declares a type parameter - a blank. The signature reads: "take an array of `T`, return a `T` or `undefined`." Call `first([1, 2, 3])` and TypeScript sees a `number[]` and *infers* `T = number` - the return type is `number | undefined` (the array could be empty, so the element might not be there), so that's what `n` is. Call it with strings and `T` becomes `string`, making `s` a `string | undefined`. The same function, written once, produces the *correct, specific* type at every call site. You almost never write `<boolean>` explicitly; inference fills `T` in from the argument.

The payoff isn't only safety - it's tooling. TypeScript now knows `n` is `number | undefined`, not `any`, so your editor red-underlines `n.toUpperCase()` - a string method a number doesn't have - instantly:

```console
Property 'toUpperCase' does not exist on type 'number'.
```

💡 **The whole win in one line.** `any` says "I don't know and I don't care." A type parameter says "I don't know *yet*, but I'll remember whatever it turns out to be."

## Multiple type parameters

A function can have more than one blank, each independent and inferred separately - like `pair`, which bundles two possibly-different-typed values into a tuple.

```typescript
function pair<A, B>(a: A, b: B): [A, B] {
  return [a, b];
}

const p = pair("id", 42);   // p is [string, number]
```

*What just happened:* Two type parameters, `A` and `B`. `pair("id", 42)` infers `A = string` from the first argument and `B = number` from the second, so the return type is `[string, number]`. The two blanks don't have to match - filled independently, exactly what you want for combining unrelated values.

## Constraints: `extends` to require *some* shape

A bare `<T>` means "literally any type," sometimes too permissive. A function logging `arg.length` needs `T` to have a `.length` - a `number` doesn't. Tell TypeScript "`T` can be any type, *as long as* it has a `length`." That's a **constraint**.

📝 **Constraint** - a requirement on a type parameter, written `<T extends Shape>`. It narrows the blank from "any type" to "any type assignable to `Shape`," letting you safely use `Shape`'s members while still accepting many concrete types.

```typescript
function logLength<T extends { length: number }>(arg: T): T {
  console.log(arg.length);   // safe: every T is guaranteed to have .length
  return arg;
}

logLength("hello");        // ok, strings have length → logs 5
logLength([1, 2, 3]);      // ok, arrays have length → logs 3
logLength(42);             // error
```

*What just happened:* `<T extends { length: number }>` constrains `T` to types with a numeric `length` property. Strings and arrays qualify - and `T` is still preserved, so `logLength([1,2,3])` returns `number[]`, not some flattened type. `42` has no `length`, so it's rejected at compile time:

```console
Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
```

Note `extends` here does *not* mean class inheritance - in a generic constraint it means "is assignable to," "has at least this shape." Same keyword, different job from the `extends` you'll see with classes next phase.

### `keyof` and safe property access

A common, powerful pattern is reading a property off an object *by key* without losing track of its type. This needs a second constraint tying one type parameter to another, via `keyof`.

📝 **`keyof T`** - an operator producing the union of `T`'s property *names* as a literal type. For `{ name: string; age: number }`, `keyof` of it is `"name" | "age"`. (Phase 10 goes deep on `keyof` and the rest of the type-operator toolkit.)

```typescript
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: "Ada", age: 36 };

const name = getProp(user, "name");   // name is `string`
const age = getProp(user, "age");     // age is `number`
getProp(user, "email");               // error: "email" isn't a key of user
```

*What just happened:* Two type parameters working together. `T` is the object's type (inferred as `{ name: string; age: number }`). `K extends keyof T` constrains `key` to one of `T`'s actual keys - `"name" | "age"`. The return type `T[K]` is a *lookup*: "the type of `T`'s property named `K`." So `getProp(user, "name")` returns `string` and `getProp(user, "age")` returns `number` - precise types from one function. Pass a nonexistent key and TypeScript stops you cold:

```console
Argument of type '"email"' is not assignable to parameter of type '"name" | "age"'.
```

This is generics earning their keep: a single helper that's fully reusable *and* fully type-safe, catching typo'd property names before the code runs.

## Generic types, interfaces, and classes

Generics aren't only for functions. Any **type alias**, **interface**, or **class** can take type parameters too - the same "blank filled in later" idea applied to data shapes.

```typescript
// A generic interface: a box that holds a value of some type T.
interface Box<T> {
  value: T;
}

const numberBox: Box<number> = { value: 7 };
const stringBox: Box<string> = { value: "hi" };

// A generic type alias: a result that's either success data or an error.
type Result<T> = { ok: true; data: T } | { ok: false; error: string };

function parseAge(input: string): Result<number> {
  const n = Number(input);
  return Number.isNaN(n)
    ? { ok: false, error: "not a number" }
    : { ok: true, data: n };
}

// A generic class: a type-safe stack.
class Stack<T> {
  private items: T[] = [];
  push(item: T): void {
    this.items.push(item);
  }
  pop(): T | undefined {
    return this.items.pop();
  }
}

const numbers = new Stack<number>();
numbers.push(1);
numbers.push(2);
const top = numbers.pop();   // top is `number | undefined`
numbers.push("oops");        // error: "oops" is not a number
```

*What just happened:* Each construct carries a type parameter filled in at the point of use. `Box<T>` becomes a concrete `Box<number>` (its `value` must be a number) or `Box<string>`. `Result<T>` is a reusable success-or-error shape - `Result<number>` here means "on success, `data` is a number." `Stack<T>` is a class where `T` flows through every method: a `Stack<number>` has `push` accepting only numbers and `pop` returning `number | undefined`. `numbers.push("oops")` is rejected at compile time - the stack *remembers* it's a stack of numbers:

```console
Argument of type 'string' is not assignable to parameter of type 'number'.
```

💡 **You've been using generics all along.** Every `Array<T>`, `Promise<T>`, and `Map<K, V>` you've written is generic - `number[]` is shorthand for `Array<number>`, and `Promise<User>` is "a promise resolving to a `User`." The angle-bracket syntax on built-in types is the exact machinery you can now use on your *own* functions, interfaces, and classes. You were already a fluent user of generics; now you're an author.

## Recap

1. **`any` throws types away; generics carry them through.** A reusable helper typed with `any` stops type-checking everywhere it's used. A generic keeps the real type intact from input to output.
2. **A type parameter (`<T>`) is a blank filled in at the call site** - almost always *inferred* from the arguments - so one function produces the correct, specific type for every caller.
3. **Type parameters are independent.** A function like `pair<A, B>` can take several, each inferred separately, letting you combine unrelated types without losing either.
4. **Constraints (`<T extends Shape>`) narrow the blank** so you can safely use a shape's members inside the function. `<K extends keyof T>` plus the lookup type `T[K]` gives type-safe property access by key.
5. ⚠️ In a generic constraint, **`extends` means "is assignable to,"** not class inheritance - "has at least this shape."
6. **Interfaces, type aliases, and classes can be generic too** (`Box<T>`, `Result<T>`, `Stack<T>`) - and you already use generic built-ins like `Array<T>`, `Promise<T>`, and `Map<K, V>` constantly.

Next, in [Phase 7](07-classes-and-oop.md), from generic *containers* to **classes and OOP** - access modifiers, inheritance, interfaces, and where that other meaning of `extends` shows up.

## Quick check

Test yourself on the one idea that drives this whole phase - that a generic *keeps* the type instead of discarding it:

```quiz
[
  {
    "q": "Why does `function first<T>(arr: T[]): T | undefined` give a better result than `function first(arr: any[]): any`?",
    "choices": [
      "The generic version infers the element type at the call site, so `first([1,2,3])` returns `number | undefined`, while the `any` version returns `any` and stops type-checking the result",
      "The generic version runs faster because TypeScript optimizes type parameters at runtime",
      "There's no real difference - `T` and `any` mean the same thing",
      "The `any` version is safer because it accepts more argument types"
    ],
    "answer": 0,
    "explain": "A type parameter is inferred from the argument and preserved in the return type, so the caller gets a precise type (`number | undefined`). `any` discards the type and switches off checking, so the result is unchecked `any`. Types are erased at runtime, so there's no speed difference."
  },
  {
    "q": "In `function getProp<T, K extends keyof T>(obj: T, key: K): T[K]`, what does the constraint `K extends keyof T` accomplish?",
    "choices": [
      "It restricts `key` to be one of `T`'s actual property names, so passing a key that doesn't exist is a compile error",
      "It makes `getProp` work only on classes that inherit from `T`",
      "It forces every property of `T` to be a string",
      "It converts `obj` into an array of its keys before returning"
    ],
    "answer": 0,
    "explain": "`keyof T` is the union of `T`'s property names, and `K extends keyof T` constrains `key` to that union - so a non-existent key like `\"email\"` is rejected. The lookup type `T[K]` then returns the precise type of that property."
  },
  {
    "q": "What does `extends` mean in a generic constraint like `<T extends { length: number }>`?",
    "choices": [
      "`T` must be assignable to that shape - i.e. it must have at least a numeric `length` property",
      "`T` must be a subclass of a class named `length`",
      "`T` is automatically given a `length` property if it doesn't have one",
      "`T` can be any type at all, with no restriction"
    ],
    "answer": 0,
    "explain": "In a generic constraint, `extends` means 'is assignable to' - 'has at least this shape.' It lets you safely use `.length` inside the function while still accepting many concrete types (strings, arrays). It is not class inheritance, despite the shared keyword."
  }
]
```


---

# Classes & OOP in TypeScript - Types on Objects With Behavior

You already know what a class *is*. Over in [JavaScript from Zero](/guides/javascript-from-zero) you saw the reveal: a `class` in JavaScript isn't a new kind of thing - it's nicer syntax over prototypes, a function whose `.prototype` is pre-loaded with your methods. None of that changes in TypeScript; the runtime is still the same JavaScript runtime.

**TypeScript doesn't give classes new powers - it adds a type layer on top of the class you already know:** field types, visibility rules, and contracts that say "this class must match that shape." All of it is checked before your code runs, and most is *erased* before it ships. The class that lands in the browser is plain JavaScript; the types were a conversation between you and the checker.

One straight caveat: TypeScript leans functional, so you won't reach for classes as often as in Java or C#. They earn their keep in two places - **stateful objects** (data and behavior bundled together, like an account) and **implementing an interface** (when something else expects a specific shape). That's where we'll focus.

## Typed fields and a typed constructor

A class field gets a type, exactly like a variable, and constructor parameters get types too. The payoff is the same as everywhere else: the checker knows what each field holds, so it catches you the moment you misuse one.

📝 **Field (class property)** - a piece of data living on each instance. Declare it with a name and type at the top of the class body, and the checker enforces that type everywhere the field is read or written.

```typescript
class Account {
  owner: string;
  balance: number;

  constructor(owner: string, initial: number) {
    this.owner = owner;
    this.balance = initial;
  }

  deposit(amount: number): void {
    this.balance += amount;
  }
}

const acct = new Account("Ada", 100);
acct.deposit(50);
console.log(acct.balance); // 150

acct.balance = "lots"; // Error
```
```console
Type 'string' is not assignable to type 'number'.
```
*What just happened:* We declared two fields with types - `owner: string` and `balance: number` - in the class body, so the checker knows the shape of every `Account`. The constructor takes typed parameters and assigns them to `this.owner` and `this.balance`; the checker verifies the types line up. `deposit` takes a `number` and returns nothing (`void`). Everything works until the last line, where assigning a string to a `number` field gets flagged at edit-time.

💡 **Key point.** Declaring fields with types is the whole reason to bother. Without it, `this.balance` would be an untyped free-for-all and a typo like `this.balnce = 50` would silently create a junk property. With it, the checker holds the line on every access.

## Access modifiers - who's allowed to touch a field

By default every field and method is `public` - reachable from anywhere. Two more keywords narrow that so internal state stays internal.

📝 **`public`** - reachable from anywhere (the default; rarely written). **`private`** - reachable only from *inside this class*. **`protected`** - reachable from inside this class *and* its subclasses, but not from outside.

`private` protects invariants: if `balance` can only change through `deposit` and `withdraw`, nobody can reach in and set it to a nonsense value behind your back.

```typescript
class Account {
  private balance: number;

  constructor(initial: number) {
    this.balance = initial;
  }

  deposit(amount: number): void {
    this.balance += amount; // fine - we're inside the class
  }
}

const acct = new Account(100);
console.log(acct.balance); // Error
```
```console
Property 'balance' is private and only accessible within class 'Account'.
```
*What just happened:* `balance` is marked `private`, so the checker allows `this.balance` inside `deposit` (same class) but rejects `acct.balance` from outside. The invariant - "balance only changes through methods" - is now enforced by the type system instead of good intentions.

⚠️ **Gotcha - TypeScript's `private` is a compile-time fiction.** It's checked by the type checker and then *erased*. At runtime the field is an ordinary public property: `(acct as any).balance` reaches it fine, and so does anyone reading the compiled JavaScript. JavaScript has its *own* truly private fields - the `#name` syntax from [JavaScript from Zero](/guides/javascript-from-zero) - enforced by the runtime and genuinely inaccessible from outside. Use TypeScript's `private` for everyday encapsulation, and reach for JS `#private` when you need a *hard* runtime guarantee. Don't mistake `private` for security - it's a design tool, not a lock.

## Parameter properties - the constructor shorthand

Look back at the first `Account`: you name `owner` and `balance` once as fields, *again* as constructor parameters, and a *third* time in the `this.x = x` assignments. That repetition is common enough that TypeScript has a shorthand collapsing all three into one.

📝 **Parameter property** - adding an access modifier (`public`, `private`, `protected`, or `readonly`) to a constructor parameter. TypeScript automatically declares a field of that name and assigns the argument to it: declaration plus assignment, in one place.

```typescript
class Account {
  constructor(
    public owner: string,
    private balance: number,
  ) {}

  deposit(amount: number): void {
    this.balance += amount;
  }
}

const acct = new Account("Ada", 100);
console.log(acct.owner); // "Ada" - public, readable
acct.deposit(50);
```
*What just happened:* Putting `public` on `owner` and `private` on `balance` told TypeScript to create those fields *and* assign the constructor arguments to them - no separate declarations, no `this.owner = owner` lines. `acct.owner` is readable because it's `public`; `balance` is `private` and locked away. Behaviorally identical to the verbose version, with a third of the lines.

💡 **Key point.** Parameter properties are unique to TypeScript and a genuine boilerplate killer - the idiomatic way to write a stateful class whose fields come straight from constructor arguments. The catch: the modifier is *required*. A bare `constructor(owner: string)` is just a normal parameter that vanishes after the constructor runs; it becomes a field only once prefixed with `public`/`private`/`protected`/`readonly`.

## `readonly`, getters and setters

Sometimes a field should be set once and never change - an ID, a creation timestamp. Mark it `readonly` and the checker forbids any write after the constructor.

```typescript
class Account {
  readonly id: string;

  constructor(id: string, private balance: number) {
    this.id = id; // allowed - we're in the constructor
  }

  // a computed, read-only view of internal state
  get summary(): string {
    return `#${this.id}: ${this.balance}`;
  }

  // validated write - guards the invariant
  set credit(amount: number) {
    if (amount > 0) this.balance += amount;
  }
}

const acct = new Account("A-1", 100);
acct.credit = 50;           // calls the setter
console.log(acct.summary);  // getter: "#A-1: 150"
acct.id = "A-2";            // Error
```
```console
Cannot assign to 'id' because it is a read-only property.
```
*What just happened:* `readonly id` can be assigned once, inside the constructor, and never again - the final line is flagged. The `get summary()` accessor exposes a *computed* string built from internal state without exposing the fields themselves; read it as `acct.summary` (no parentheses - looks like a field but runs code). The `set credit(...)` accessor runs validation on assignment, so `acct.credit = 50` looks like a plain write but invokes a guarded method (a distinct name from the earlier `deposit()` method, so the setter isn't confused with it). Getters and setters are typed like any other member.

## `implements` and `abstract` - contracts and forced shapes

Here's where classes really pull their weight: stating that a class *fulfills a contract*.

📝 **`implements`** - a promise that a class matches an interface's shape. The checker verifies every required member is present with a compatible type. Forget one, or get a type wrong, and it's an error on the class, where you can fix it - not at some distant call site.

```typescript
interface Persistable {
  id: string;
  save(): void;
}

class Account implements Persistable {
  constructor(public id: string, private balance: number) {}

  save(): void {
    console.log(`saving ${this.id}`);
  }
}
```
*What just happened:* `Account implements Persistable` tells the checker "verify this class has everything `Persistable` requires." It has `id: string` (via a parameter property) and a `save(): void` method, so it compiles. Drop `save`, or type `id` as a `number`, and the error lands right on the `class Account` line.

Now the other tool: a base class that **can't be instantiated on its own** and forces subclasses to fill in specific methods.

📝 **`abstract`** - a class marked `abstract` can't be created with `new`; it exists only to be extended. An `abstract` method has no body - a required slot every concrete subclass must implement.

```typescript
abstract class Shape {
  abstract area(): number;       // no body - subclasses must provide one

  describe(): string {           // shared, concrete behavior
    return `area is ${this.area()}`;
  }
}

class Circle extends Shape {
  constructor(private radius: number) {
    super();
  }
  area(): number {
    return Math.PI * this.radius ** 2;
  }
}

const c = new Circle(2);
console.log(c.describe());  // "area is 12.566..."
const s = new Shape();      // Error
```
```console
Cannot create an instance of an abstract class.
```
*What just happened:* `Shape` declares an abstract `area()` with no implementation and a concrete `describe()` that *calls* it. `Circle extends Shape` supplies a real `area()`, so it's a complete, instantiable class. `new Shape()` directly is rejected - the base is a template, not a usable object. If `Circle` had forgotten `area()`, that omission would be flagged on `Circle` too. Abstract classes share real code in the base while *guaranteeing* every subclass fills in the missing pieces.

💡 **Key point - `implements` vs `extends`.** `implements` means "I promise to *match this shape*" - copies no code, just enforces a contract, and a class can implement many interfaces. `extends` means "I *reuse this base*" - you inherit its fields and methods, and a class extends exactly one base. Use `implements` when callers care that you fit a shape; use `extends` (often with `abstract`) to share behavior down a hierarchy.

## Recap

1. TypeScript classes are the **same JavaScript classes** you already know (sugar over prototypes) plus a **type layer** - field types, visibility, and contracts checked before the code runs.
2. **Typed fields and a typed constructor** let the checker enforce what each instance holds; **parameter properties** (`constructor(private balance: number)`) declare and assign a field in one line - a TS-only boilerplate killer.
3. **`public` / `private` / `protected`** control access, but ⚠️ TS `private` is *compile-time only and erased*; for a runtime-enforced field use JavaScript's `#private`.
4. **`readonly`** locks a field after the constructor; **getters/setters** expose computed views or validated writes that read like plain fields.
5. **`implements`** verifies a class matches an interface's shape (a contract, no code reuse); **`abstract`** defines a base that can't be instantiated and forces subclasses to fill in methods.
6. Reach for classes mainly for **stateful objects** and **implementing interfaces** - TypeScript leans functional, so don't force OOP where a plain function or object would do.

You can now put types on objects with behavior. Next, we leave the language and wire it into a real project: **modules, `tsconfig.json`, and the build** that turns typed source into shippable JavaScript.

## Quick check

Test yourself on the three ideas that matter most here - what `private` really does, the parameter-property shorthand, and `implements` vs `abstract`:

```quiz
[
  {
    "q": "You mark `balance` as `private` in a TypeScript class, then read it at runtime with `(acct as any).balance`. What happens?",
    "choices": [
      "It works - TS `private` is a compile-time check that's erased, so the field is an ordinary public property at runtime",
      "It throws a runtime error, because `private` fields are sealed by the JavaScript engine",
      "It returns `undefined`, because the field doesn't exist outside the class",
      "It fails to compile, because `as any` can never reach a private field"
    ],
    "answer": 0,
    "explain": "TypeScript's `private` is enforced only by the type checker and then erased. At runtime the field is a normal property, fully reachable. For a runtime-enforced private field, use JavaScript's `#name` syntax instead."
  },
  {
    "q": "What does the constructor `constructor(private balance: number) {}` do that a plain `constructor(balance: number) {}` does not?",
    "choices": [
      "It declares a `balance` field and assigns the argument to it automatically - declaration and assignment in one line",
      "It makes the constructor run faster by skipping the assignment step",
      "It marks the whole class as private so it can't be instantiated",
      "Nothing different - the modifier on a parameter is ignored at compile time"
    ],
    "answer": 0,
    "explain": "An access modifier on a constructor parameter is a 'parameter property': TypeScript declares a field of that name and assigns the argument to it. A bare parameter (no modifier) is just a local that disappears when the constructor returns."
  },
  {
    "q": "When should you reach for `implements` instead of `extends`?",
    "choices": [
      "When you want to promise a class matches an interface's shape, without inheriting any code - and a class can implement many interfaces",
      "When you want to copy all the methods and fields from a base class into your class",
      "Whenever you use an abstract class, since `implements` and `abstract` mean the same thing",
      "Only when the base class has no methods to inherit"
    ],
    "answer": 0,
    "explain": "`implements` enforces a contract - the checker verifies your class has every member the interface requires - but copies no code, and you can implement many interfaces. `extends` reuses a base class's actual code, and you extend exactly one."
  }
]
```


---

# Modules, tsconfig & the Build - Configuring the Compiler

For seven phases you've been writing types and pretending the compiler reads them somehow. This phase names
that "somehow" and turns a folder of `.ts` files into a *real project*: code split across files that import
each other, a config file that tells the compiler how to check and build, and an answer to "wait, who
actually turns my `.ts` into `.js`?"

Here's the mental model to hold onto: **TypeScript is a checker bolted onto a compiler, and `tsconfig.json`
is the dial board for both.** Every option either changes *how strictly it checks* or *what JavaScript it
emits*. Seen through that lens, the file stops looking like an intimidating wall of JSON and starts looking
like a short list of decisions you actually understand.

## Modules in TypeScript - same import/export you already know

TypeScript doesn't invent its own module system - it uses **ES modules**, the exact `import` / `export`
syntax from JavaScript. If you've worked through the [JavaScript guide](/guides/javascript-from-zero), this
is the same `import { thing } from "./file"` you already know.

What *is* new: both **values** and **types** travel through those same statements. You export a function (a
value) the same way you export an `interface` (a type).

```typescript
// money.ts
export interface Money {
  amount: number;
  currency: string;
}

export function format(m: Money): string {
  return `${m.amount.toFixed(2)} ${m.currency}`;
}

// checkout.ts
import { Money, format } from "./money";

const total: Money = { amount: 19.99, currency: "USD" };
console.log(format(total));
```

`Money` is a type - exists only at check-time, vanishes from the output. `format` is a value - real runtime
code. `checkout.ts` imports both with one `import` line, and TypeScript sorts out which is which: `Money`
type-checks `total`, while `format` becomes a genuine function call in the emitted JavaScript.

### `import type` - saying "this is types only"

Sometimes you import *only* a type from another file - no functions, no runtime values. Mark that intent
explicitly with **`import type`**; it's worth the habit.

```typescript
// checkout.ts
import type { Money } from "./money";
import { format } from "./money";

const total: Money = { amount: 19.99, currency: "USD" };
console.log(format(total));
```

`import type { Money }` tells the compiler "I need this name only for type-checking - it has no runtime
existence." When TypeScript emits JavaScript, that line is **erased completely**. A plain `import { Money }`
might leave behind an `import "./money"` statement that a bundler then has to resolve and possibly include,
even though `Money` is just a shape.

💡 **Why bother with `import type`.** It does two jobs: guarantees the import is dropped from the build, so
you never accidentally pull a whole module into your bundle just to reference one interface; and documents
intent, sidestepping a class of circular-dependency headaches. Prefer it for type-only imports.

## `tsconfig.json` - the control panel

The compiler checks your types and emits JavaScript. One file decides what to check and what to emit.

📝 **`tsconfig.json`** - a JSON file at the root of your project that tells `tsc` how to behave: which files
to include, how strictly to type-check them, and what kind of JavaScript to produce. Run `tsc` in a folder
containing this file and it reads it automatically - no flags needed. Your editor reads it too, which is why
VS Code's red underlines match what the command line reports.

A sensible starter config - the kind you'd happily drop into a new project today:

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "outDir": "./dist",
    "rootDir": "./src",
    "lib": ["ES2022", "DOM"],
    "strict": true,
    "sourceMap": true,
    "declaration": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}
```

Everything lives under `compilerOptions`, plus an `include` array saying "compile every file under `src/`."
Each option is one decision about checking or emitting - don't memorize the list, the next two sections cover
the ones that earn their place. This file is the entire contract between you and the compiler; change a
value here and the behavior of `tsc` (and your editor) changes everywhere.

## The options that actually matter

You can ignore most of `tsconfig`'s long option list for a long time. The handful you'll set on day one, one
sentence of *why* each:

- **`target`** - which version of JavaScript to emit. Newer (`ES2022`) means cleaner output assuming a
  modern runtime; older (`ES5`) adds compatibility shims for ancient browsers. Pick the oldest environment
  you must support.
- **`module`** - the module *format* of the emitted code (`ESNext` for `import`/`export`, `CommonJS` for
  Node's older `require`). Decides how your `import` lines look after compilation.
- **`outDir`** / **`rootDir`** - where compiled `.js` files go (`outDir`) and where your source `.ts` lives
  (`rootDir`). Keeping output in `dist/` and source in `src/` stops generated files cluttering your code.
- **`lib`** - which built-in type definitions are available. Add `"DOM"` and the compiler knows about
  `document` and `window`; omit it for a pure Node project so browser globals don't sneak in.
- **`sourceMap`** - emit `.map` files so debuggers and stack traces point back to your original TypeScript
  instead of the compiled JavaScript. Turn it on; future-you debugging production will be grateful.
- **`declaration`** - emit `.d.ts` files alongside the JavaScript, letting *other* TypeScript projects
  consume your code with full types - essential for a published library, ignorable for a leaf app.

⚠️ **`target` is about *output*, not what you can write.** Setting `target: "ES2022"` doesn't unlock new
syntax for you - you can already write any modern TypeScript. It controls what the *emitted JavaScript* looks
like and which runtime features it assumes exist. Too new and your code may use syntax an old browser chokes
on; too old and `tsc` bloats the output down-leveling features nobody needed.

## `strict` mode - turn it on, always

Of every option above, one matters more than all the others combined. If you remember a single line from
this phase, make it `"strict": true`.

📝 **`strict`** - a master switch that turns on a bundle of the compiler's strongest safety checks at once,
including **`strictNullChecks`** (treat `null` and `undefined` as their own types you must handle, not
silent members of every type) and **`noImplicitAny`** (refuse to silently give a value the escape-hatch `any`
type when it can't infer one). It's the difference between TypeScript that *catches bugs* and TypeScript that
mostly nods along.

The most valuable thing in that bundle is `strictNullChecks`. Without it, `null` and `undefined` quietly
belong to *every* type, so the compiler waves through code that will explode at runtime:

```typescript
// With strictNullChecks OFF (strict: false), this compiles cleanly:
function firstChar(name: string): string {
  return name[0].toUpperCase();
}

const users: { name?: string } = {};
firstChar(users.name); // name is undefined - boom at runtime, no compiler warning
```

`users.name` is optional, so it's `undefined` here. With strict mode off, the compiler treats `undefined` as
an acceptable `string` and lets you pass it to `firstChar`. At runtime, `undefined[0]` throws `Cannot read
properties of undefined` - the exact bug TypeScript is supposed to prevent, sailing straight through because
the safety net was off.

Now turn `strict: true` on:

```typescript
// With strictNullChecks ON, the same call is a compile error:
function firstChar(name: string): string {
  return name[0].toUpperCase();
}

const users: { name?: string } = {};
firstChar(users.name);
// Error: Argument of type 'string | undefined' is not
// assignable to parameter of type 'string'.
```

Now the compiler knows `users.name` is `string | undefined` and that `firstChar` only accepts `string`. It
refuses to compile until you handle the `undefined` case - with a default, a guard (`if (users.name)`), or
the narrowing from [Phase 5](05-unions-and-narrowing.md). The bug is caught *at edit-time*, before the code
ever runs. That's the entire point of TypeScript, and it only works with strict on.

⚠️ **Never start a new project with strict off.** Turning strict on later, after thousands of lines, surfaces
a mountain of errors at once and tempts everyone to slap `any` everywhere or give up. Strict from line one
keeps the cost continuous and tiny. The only good reason to disable pieces of it is gradually migrating a
giant legacy codebase - and even then, turn the checks *on* one at a time, never leave them off forever.

## The build - where TypeScript meets the bundler

One last thing trips up nearly everyone: TypeScript can't run in a browser or Node directly, so *something*
has to turn `.ts` into `.js`. Two common arrangements:

**Option 1 - `tsc` does the build.** Run `tsc` and it both type-checks and emits JavaScript into `outDir`.
Simple, no extra tools, perfect for a library or a small Node program.

```bash
$ tsc
$ node dist/checkout.js
19.99 USD
```

`tsc` checked every file under `src/` and wrote compiled JavaScript to `dist/`. Then plain `node` ran the
output. One tool, whole job done.

**Option 2 - a bundler builds, `tsc` only checks.** Real front-end apps more commonly split the work. A fast
bundler or transpiler (Vite, esbuild, swc) handles the `.ts` → `.js` step as part of bundling, alongside
tree-shaking, code-splitting, and the other things from the
[JavaScript modules & bundlers phase](/guides/javascript-from-zero). Those tools are *fast* because they
**strip types without checking them**. So `tsc` runs separately, purely as the type checker, with a flag
that tells it to check and emit nothing:

```bash
$ tsc --noEmit
$ vite build
```

`tsc --noEmit` type-checked the whole project and produced *zero* output - its only job is to say "the types
are sound" (or fail the build if not). Then `vite build` did the actual transformation and bundling. Two
tools, two jobs: one guards correctness, the other produces the shippable files.

💡 **In real apps, the bundler builds and `tsc` just checks.** This split confuses people because the type
*errors* and the actual *build* come from different tools. It's worth it because bundlers transpile blazingly
fast precisely by *not* type-checking, giving instant rebuilds during development, while a separate
`tsc --noEmit` (often in CI, or a watch task) enforces type safety without slowing the build. The types still
protect you - verified by `tsc`, just not by the thing producing your JavaScript.

That closes the loop: a checker, a config file driving it, the settings that matter, strict mode keeping it
accurate, and a clear picture of who emits the JavaScript.

## Recap

1. **TypeScript uses ES modules** - the same `import`/`export` as JavaScript - and both **types and values**
   travel through them; the compiler sorts out which is which.
2. **`import type { ... }`** marks a type-only import so it's fully erased from the build, keeping types out
   of your bundle and documenting that nothing runtime crosses that boundary.
3. **`tsconfig.json`** is the control panel: it tells `tsc` (and your editor) which files to include, how
   strictly to check, and what JavaScript to emit. `tsc` reads it automatically.
4. The options that earn their keep: **`target`** (which JS version to emit), **`module`** (format),
   **`outDir`/`rootDir`**, **`lib`**, **`sourceMap`**, and **`declaration`** - each a single decision about
   checking or output.
5. ⚠️ **`strict: true` is non-negotiable for new projects** - it bundles `strictNullChecks`, `noImplicitAny`,
   and more, and makes TypeScript actually catch `null`/`undefined` bugs instead of nodding along.
6. **`tsc` can build, or a bundler builds while `tsc --noEmit` just checks** - the common front-end setup,
   where Vite/esbuild produce fast output and `tsc` guards correctness separately.

## Quick check

Test yourself on the three ideas that make a project real - type-only imports, the config dial board, and
strict mode:

```quiz
[
  {
    "q": "What does writing `import type { Money } from \"./money\"` (instead of a plain `import`) guarantee?",
    "choices": [
      "The import is fully erased from the emitted JavaScript, so it never pulls runtime code into your bundle",
      "It loads the module faster at runtime than a regular import",
      "It converts the interface into a runtime object you can inspect",
      "It makes `Money` available without needing to export it from money.ts"
    ],
    "answer": 0,
    "explain": "`import type` tells the compiler the name is needed only for type-checking. The line is dropped entirely from the build, so you never accidentally bundle a whole module just to reference one type - and it documents that nothing runtime crosses that boundary."
  },
  {
    "q": "What is the role of `tsconfig.json` in a TypeScript project?",
    "choices": [
      "It tells the compiler (and your editor) which files to include, how strictly to type-check, and what JavaScript to emit - read automatically by `tsc`",
      "It lists the npm packages your project depends on",
      "It stores the compiled JavaScript output of your project",
      "It is a runtime file the browser reads to enable TypeScript features"
    ],
    "answer": 0,
    "explain": "`tsconfig.json` is the control panel for `tsc`. Run `tsc` in a folder that has one and it's read automatically - no flags. Your editor reads it too, which is why its red underlines match the command line."
  },
  {
    "q": "Why is turning on `strict` mode the single most important `tsconfig` setting for a new project?",
    "choices": [
      "It bundles `strictNullChecks`, `noImplicitAny`, and more - making the compiler actually catch null/undefined bugs instead of silently allowing them",
      "It makes the compiled JavaScript run faster in the browser",
      "It automatically adds type annotations to your code for you",
      "It lets you skip writing types entirely while staying type-safe"
    ],
    "answer": 0,
    "explain": "`strict: true` enables the compiler's strongest checks at once. The key one, `strictNullChecks`, stops `null`/`undefined` from silently belonging to every type - which is what lets TypeScript catch the bugs it exists to catch. Start new projects with it on."
  }
]
```


---

# The Type System, Deep - Structural Typing & How Inference Works

You've spent eight phases *using* TypeScript's types. This phase covers how the checker actually *thinks* - the rules it follows when deciding whether one type fits another, and where the types you never wrote come from. It's the advanced half of the guide, and it pays off everywhere: once you understand these mechanics, the checker's most baffling errors (and its most surprising silences) become predictable.

Here's the mental model to carry through the whole phase: **TypeScript judges types by their *shape*, not their name, and it does an enormous amount of guessing on your behalf.** Most of the time you never write a type annotation - TypeScript infers one. Knowing *which* type it infers, and *why*, is the difference between fighting the checker and steering it.

## Structural typing - "duck typing for types"

The first thing to internalize is how TypeScript decides whether two types are the same, or compatible. Coming from Java or C#, you'd expect this to hinge on names - a value is a `Point` only if declared as one. TypeScript does the opposite.

📝 **Structural typing** - types are compared by their *structure* (the members they have), not their declared name. If a value has at least all the members a target type requires, it's compatible with that target, even if the two were never related on paper. The contrast is **nominal typing** (Java, C#), where compatibility depends on the explicit name in the declaration.

You may know this idea from runtime languages as "duck typing": if it walks like a duck and quacks like a duck, treat it as a duck. TypeScript applies the same principle to *static* types.

```typescript
interface Point {
  x: number;
  y: number;
}

function printPoint(p: Point): void {
  console.log(`(${p.x}, ${p.y})`);
}

// Never declared as a Point - just an object with x and y.
const location = { x: 10, y: 20 };
printPoint(location); // ✅ accepted

// A class that has no idea Point exists.
class Vector {
  constructor(public x: number, public y: number) {}
}
printPoint(new Vector(3, 4)); // ✅ also accepted
```

*What just happened:* Neither `location` nor `Vector` mentions `Point` anywhere. In Java this would be a compile error - they're not declared to implement the interface. TypeScript only checks the *shape*: does this value have an `x: number` and a `y: number`? Both do, so both are valid `Point`s. The name is just a label for humans; the checker reasons entirely about members.

💡 **Why this design.** JavaScript objects are bags of properties created on the fly - from JSON, literals, libraries that never heard of your types. Nominal typing would reject all of them. Structural typing lets TypeScript describe code that already exists without retrofitting `implements` clauses onto every object - the rule that makes it practical to bolt onto real JavaScript.

## Assignability & the excess-property surprise

Structural typing is governed by one core question: **is type X assignable to type Y?** Get this phrasing right and most type errors decode themselves.

📝 **Assignability** - X is assignable to Y if X has *at least everything Y requires*. Y is the contract ("I need an `x` and a `y`"); X satisfies it as long as it provides those, regardless of extra members. More is fine; missing a required one is not.

That "more is fine" direction surprises people, so look at it directly:

```typescript
interface Named {
  name: string;
}

const employee = { name: "Ada", salary: 90000 };

const n: Named = employee; // ✅ employee has name (plus extra) - assignable
```

`Named` requires only a `name: string`. `employee` has that, plus a `salary` field, which doesn't disqualify it - `employee` provides everything `Named` asks for, so it's assignable. Through the variable `n`, TypeScript only lets you see `.name`, but the value underneath still carries `salary` at runtime.

Now the gotcha that bites everyone:

⚠️ **Excess-property checks fire on object *literals* only.** When you assign a fresh object literal directly to a typed target, TypeScript runs an *extra* check that rejects properties the target doesn't declare - contradicting the "more is fine" rule above, on purpose, to catch typos. Route the same object through an intermediate variable and the check vanishes.

```typescript
interface Options {
  width: number;
  height: number;
}

// Direct literal - excess-property check fires.
const a: Options = { width: 100, height: 50, depth: 10 };
```
```console
Object literal may only specify known properties, and 'depth' does not
exist in type 'Options'.
```

The fix - and the reason the rule exists - is assigning through a variable, which downgrades the check to ordinary assignability:

```typescript
interface Options {
  width: number;
  height: number;
}

const raw = { width: 100, height: 50, depth: 10 };
const b: Options = raw; // ✅ no excess-property check - plain assignability
```

The first version hands a brand-new literal straight to an `Options` variable. TypeScript assumes a literal written *right there* should match the target exactly, so the stray `depth` is almost certainly a typo (maybe you meant `width`?) and it errors. The second version assigns `raw` first; by the time it reaches `b`, it's a *value*, not a literal, so the normal rule applies - `raw` has everything `Options` needs, extra `depth` and all, so it's assignable. Same object, different rule: one is a literal at the point of assignment, the other isn't.

💡 The excess-property check is a deliberate, narrow exception to structural typing - aimed at the single most common mistake, a misspelled property name in a literal. When you genuinely want the extra property, the intermediate-variable form tells the checker "I meant to do this."

## Type widening - why `let` and `const` infer differently

When you don't annotate, TypeScript infers a type. But it doesn't always infer the *narrowest* possible one - it sometimes **widens** a literal to its general type, depending on whether the binding can change.

📝 **Type widening** - when TypeScript infers a type from a literal value, it broadens (widens) the literal to its general type for mutable bindings. `let x = "hi"` infers `string`, not the literal type `"hi"`, since you might reassign `x` later. A `const` can never be reassigned, so `const x = "hi"` keeps the exact literal type `"hi"`.

```typescript
let mutable = "hi";       // inferred type: string
const immutable = "hi";   // inferred type: "hi"  (a literal type)

mutable = "bye";          // ✅ fine - string accepts any string
// immutable = "bye";     // would error - "hi" accepts only "hi"

let count = 42;           // inferred: number
const max = 42;           // inferred: 42
```

`mutable` is a `let`, so it could be reassigned to any other string - inferring the locked-down literal `"hi"` would make `mutable = "bye"` an error, which would be absurd. TypeScript widens it to `string`. `immutable` is a `const` that physically cannot change, so TypeScript keeps the most precise type it knows, the literal `"hi"`. Same split for `count` (`number`) versus `max` (`42`).

💡 **Why this matters.** Literal types power discriminated unions, exhaustive `switch` checks, and precise function arguments. When you *want* that precision, `const` preserves it; when you want flexibility, `let` gives you the general type. The kind of binding you choose silently shapes the type you get.

## `as const` - freezing a value to its narrowest types

`const` only stops *reassignment of the variable*. It does nothing for the *contents* of an object or array - those still get widened, member by member. To freeze everything inside to its exact literal types, reach for `as const`.

📝 **`as const`** - a *const assertion* applied to a value. It tells TypeScript to infer the narrowest possible type: every member becomes its exact literal type, and the whole structure becomes deeply `readonly`. It's the tool for fixed configuration objects and discriminated-union tags.

Watch the difference:

```typescript
// Without as const - members are widened.
const config1 = { mode: "dark", retries: 3 };
// inferred: { mode: string; retries: number }
//   config1.mode is just string - "light", "anything" would type-check

// With as const - members are pinned and readonly.
const config2 = { mode: "dark", retries: 3 } as const;
// inferred: { readonly mode: "dark"; readonly retries: 3 }
//   config2.mode is exactly "dark", and you can't reassign it
```

In `config1`, the outer `const` stops you from reassigning the whole variable, but each property is inferred with widening - `mode` becomes `string`, `retries` becomes `number`. In `config2`, `as const` flips every member to its literal type (`"dark"`, `3`) and marks them all `readonly`: a precise, immutable description of itself.

The canonical fix for losing literal types where you need them - most often a union tag:

```typescript
type Action =
  | { type: "increment"; by: number }
  | { type: "reset" };

// Without as const, `type` widens to string and won't match the union.
const bad = { type: "increment", by: 1 };
// const result1: Action = bad; // ❌ string not assignable to "increment"

const good = { type: "increment", by: 1 } as const;
const result2: Action = good; // ✅ type is exactly "increment"
```

`Action` is a discriminated union keyed on the literal `type` field. Plain inference widens `bad.type` to `string`, which fits neither branch, so the assignment fails. `as const` keeps `good.type` as the literal `"increment"`, matching the first branch, and the assignment succeeds. Any time an object needs to *be* a specific union member, `as const` is how you keep its tag literal.

## How inference flows - let TypeScript do the work

You've seen TypeScript infer from values. It also infers from *context* - surrounding code tells it what a type should be, so you don't have to annotate.

📝 **Contextual typing** - TypeScript infers a value's type from the position it appears in. The classic case: a callback's parameter types are inferred from the function that receives it.

```typescript
const nums = [1, 2, 3];

// No annotation on n - TypeScript knows nums is number[],
// so .map's callback parameter must be a number.
const doubled = nums.map((n) => n * 2); // n: number, doubled: number[]

const words = ["a", "bb", "ccc"];
const lengths = words.map((w) => w.length); // w: string, lengths: number[]
```

You never wrote a type for `n` or `w`. Because `nums` is `number[]`, `.map`'s callback parameter is fixed to `number` by context, so `n` is a `number`; `words` is `string[]`, so `w` is a `string`. The *return* type is inferred too - `n * 2` is a `number`, so `doubled` is `number[]`. Annotating any of these would be noise; context already pins them down.

Return-type inference works the same way for your own functions - TypeScript reads the `return` statement:

```typescript
function makeUser(name: string, age: number) {
  return { name, age, active: true };
  // inferred return type: { name: string; age: number; active: boolean }
}
```

You annotated the *parameters* (the boundary, where data enters) but not the return - TypeScript computed that from the object you return. Adding `: { name: string; age: number; active: boolean }` would just restate what it already knows, and risk drifting out of sync if you later add a field.

💡 **The practical rule.** Let inference do the work *inside* your code; annotate at the *boundaries* and when you want to *pin* a type. Annotate function parameters and exported/public signatures so callers get a stable contract and errors point at the right place. Skip annotations on local variables, callback parameters, and clearly-correct return types - over-annotating is a common beginner habit that adds noise and lets types drift from reality.

One modern tool deserves a mention here: sometimes you want to *check* a value against a type without *widening it to that type* - keeping the precise inferred type for later use. That's `satisfies`:

```typescript
type Theme = Record<string, string>;

// `: Theme` would widen palette to Record<string, string>,
// losing the specific keys. `satisfies` checks AND keeps them.
const palette = {
  primary: "#2563eb",
  danger: "#dc2626",
} satisfies Theme;

palette.primary; // ✅ still known to exist
// palette.missing; // ❌ caught - not a key of palette
```

Annotating `const palette: Theme` would verify the shape but then treat `palette` as a plain `Record<string, string>`, forgetting the specific keys (`primary`, `danger`). `satisfies Theme` runs the same compatibility check - every value must be a `string` - but leaves `palette`'s narrow inferred type intact, so you keep autocomplete and key-existence checks: "validate against a type, but don't widen to it."

## Recap

1. **Structural typing** - TypeScript compares types by their *shape* (members), not declared name. An unrelated object or class is accepted anywhere its members satisfy the target - "duck typing" for static types, and what makes TS fit real JavaScript.
2. **Assignability** means X provides *at least everything Y requires* - extra properties are fine. The exception is the **excess-property check**, firing only on object *literals* assigned directly to a typed target (to catch typos); an intermediate variable downgrades it to plain assignability.
3. **Widening**: inference from a literal broadens to the general type for mutable bindings (`let x = "hi"` → `string`) but keeps the exact literal for `const` (`const x = "hi"` → `"hi"`), since a `const` can never change.
4. **`as const`** freezes a value to its narrowest literal types and makes it deeply `readonly` - essential for fixed config and for keeping discriminated-union tags literal instead of widened to `string`.
5. **Inference flows from context**: callback parameters are typed by where they're used (`arr.map(x => ...)` knows `x`), and return types come from `return` statements. Annotate boundaries, let inference handle the inside; `satisfies` checks a value against a type without widening it.

With the checker's reasoning demystified, you're ready to *transform* types programmatically - utility and mapped types build new types out of existing ones, leaning directly on the assignability and inference rules you just learned.

## Quick check

Lock in the ideas most likely to trip you up - shape-based compatibility, the excess-property exception, and what `as const` preserves:

```quiz
[
  {
    "q": "A function expects a `Point` (interface with `x: number; y: number`). You pass `new Vector(3, 4)`, a class that never mentions `Point`. Why does TypeScript accept it?",
    "choices": [
      "TypeScript uses structural typing - Vector has the required `x` and `y` members, so its shape matches `Point` regardless of its name",
      "TypeScript silently converts the Vector into a Point at runtime",
      "Classes are exempt from type checking when passed to functions",
      "It only works because Vector and Point happen to start with similar letters"
    ],
    "answer": 0,
    "explain": "TypeScript compares by shape, not name. `Vector` has `x: number` and `y: number`, which is everything `Point` requires, so it's assignable. The declared name is irrelevant - that's structural (duck) typing."
  },
  {
    "q": "`const a: Options = { width: 100, height: 50, depth: 10 }` errors on `depth`, but assigning the same object through a variable first does not. Why?",
    "choices": [
      "Excess-property checks fire only on object literals assigned directly to a typed target; an intermediate variable falls back to ordinary assignability, where extra properties are allowed",
      "The variable version deletes the `depth` property automatically",
      "Object literals are immutable and variables are not, so the rules differ",
      "It's a compiler bug - both forms should error"
    ],
    "answer": 0,
    "explain": "The excess-property check is a special case aimed at catching typos in literals. It applies only to a literal assigned straight to a typed slot. Through a variable, the normal 'at least everything required' rule applies, and extra properties are fine."
  },
  {
    "q": "You write `const action = { type: \"increment\", by: 1 }` and try to assign it to a discriminated union keyed on `type`. It fails. What fixes it?",
    "choices": [
      "Add `as const` so `type` keeps its literal type \"increment\" instead of being widened to `string`",
      "Change `const` to `let` so the type becomes mutable",
      "Remove the `by` field so the object is smaller",
      "Nothing - discriminated unions can't be built from object literals"
    ],
    "answer": 0,
    "explain": "Plain inference widens `type` to `string`, which matches no branch of the union. `as const` pins every member to its exact literal type, so `type` stays \"increment\" and the object matches that branch of the union."
  }
]
```


---

# Utility & Mapped Types - Deriving Types From Types

By now you can describe the shape of your data with interfaces and type aliases. But real codebases don't have *one* type per concept - they have a whole family of near-identical ones: a `User`, a `UserUpdate` where every field is optional, a `NewUser` with no `id` yet, a `ReadonlyUser` the cache hands back. Write all four by hand and you've signed up for a maintenance nightmare: add a field to `User`, and you must remember the other three. Forget one, and the type system happily lets the bug through.

Here's the mental model for this whole phase: **stop hand-maintaining parallel types - derive one type from another so they can never drift apart.** Instead of copy-pasting `User`'s fields into `UserUpdate`, say "`UserUpdate` is `User` with everything optional" and let the compiler compute it. When `User` changes, every derived type updates automatically, for free. This is where the type system stops being labels you stick on values and becomes a small language you *compute* with.

## `keyof` and indexed access - the building blocks

Everything in this phase is built from two tiny operators - learn these and the rest is recombination.

📝 **`keyof T`** - an operator producing the union of `T`'s property names *as a type*. If `User` has `id`, `name`, and `email`, `keyof User` is the type `"id" | "name" | "email"`.

📝 **Indexed access (`T["key"]`)** - looks up the *type* of a property by its key, the way you'd index a value with `obj["key"]`, except at the type level. `User["id"]` is whatever type `id` was declared as.

```typescript
interface User {
  id: number;
  name: string;
  email: string;
}

type UserKeys = keyof User;     // "id" | "name" | "email"
type IdType = User["id"];       // number
type NameOrId = User["name" | "id"]; // string | number
```

`keyof User` collected the three property names into a union of string-literal types - a value of type `UserKeys` can only ever be one of those three strings. `User["id"]` reached into the interface and pulled out the *type* at `id`, which was `number`. Because you can index with a union, `User["name" | "id"]` returned the union of *both* property types, `string | number`. These two operators are the gears; the rest of the phase is machines built from them.

💡 **Why this is powerful:** `keyof` and indexed access read the type *as it is right now*. Nothing to keep in sync - add a property to `User` and `keyof User` grows automatically. That self-updating quality is the foundation everything else depends on.

## The built-in utility types - derivations you'll use daily

TypeScript ships a standard library of pre-built derivations. Each takes a type and hands back a transformed version. You don't import them; they're always in scope. The six you'll reach for constantly:

```typescript
interface User {
  id: number;
  name: string;
  email: string;
}

type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string }

type RequiredUser = Required<User>;
// every field non-optional

type ReadonlyUser = Readonly<User>;
// { readonly id: number; readonly name: string; readonly email: string }

type PublicUser = Pick<User, "id" | "name">;
// { id: number; name: string }   - keep only the named keys

type CreatePayload = Omit<User, "id">;
// { name: string; email: string }   - drop the named keys

type UsersById = Record<number, User>;
// { [key: number]: User }   - a lookup object
```

Each utility computed a new type from `User` without restating a single field. `Partial<User>` made every property optional - exactly the shape an `updateUser(id, changes)` function wants, since a caller should send only the fields they're changing. `Omit<User, "id">` dropped `id` for a *create* request, where the server assigns the id. `Pick<User, "id" | "name">` kept only the two safe-to-expose fields for a public API. `Record<number, User>` built a lookup object keyed by user id - an in-memory cache's type. None of these were hand-written; all track `User` automatically.

The everyday signatures look like this:

```typescript
// Partial<T> for updates - caller sends only what changed
function updateUser(id: number, changes: Partial<User>): void { /* ... */ }

// Omit<T, K> for create payloads - no id yet
function createUser(payload: Omit<User, "id">): User { /* ... */ }

// Record<K, V> for lookups
const cache: Record<number, User> = {};
```

💡 **The bug class these kill:** the "I changed `User` but forgot to update `UserUpdate`" family. Add a `phone` field to `User`, and `Partial<User>`, `Omit<User, "id">`, and `Record<number, User>` *all* gain it the instant you save. A hand-maintained parallel type would silently fall behind, surfacing only when something broke at runtime - far from the line you actually changed.

⚠️ **`Omit` doesn't warn on a typo'd key.** `Omit<User, "emial">` (misspelled) doesn't error - it just omits nothing and returns `User` unchanged, because `Omit`'s key parameter accepts any string, not only real keys of `User`. This is still true in current TypeScript, so if a `Pick`/`Omit` misbehaves, check the key spelling first. `Pick<User, "emial">` *does* error, since `Pick`'s key parameter is constrained to `keyof T`.

## Mapped types - the engine underneath

Those utility types feel like magic until you see what's inside: they're all built from one construct, the **mapped type**.

📝 **Mapped type** - a type that builds a new object type by *iterating over the keys of another type*, using the syntax `{ [K in keyof T]: ... }`. For each key `K` in `T`, it produces one property in the result - a `for` loop over the keys of a type, running at compile time.

The clearest way to see it: the *actual definition* of `Partial<T>` from TypeScript's standard library.

```typescript
type Partial<T> = {
  [K in keyof T]?: T[K];
};
```

Read it left to right. `[K in keyof T]` says "for each key `K` in the union `keyof T`." The `?` after the bracket makes that property optional. `T[K]` - indexed access, from the first section - looks up the original type of that property and keeps it. So `Partial<User>` walks `"id" | "name" | "email"`, emitting an optional property of the same type for each. The "magic" utility type is four lines you can now read - every built-in in the previous section is a variation on this pattern.

Now write your own: a type where every field of `User` becomes a `boolean` flag - useful for tracking which fields a form has touched:

```typescript
type Flags<T> = {
  [K in keyof T]: boolean;
};

type UserTouched = Flags<User>;
// { id: boolean; name: boolean; email: boolean }
```

`Flags<T>` iterated `keyof T` exactly like `Partial` did, but instead of reusing `T[K]` for each property's type, it hard-coded `boolean`. The result has the same *keys* as `User` but a uniform value type. You've written a custom derivation - `UserTouched` will gain a `boolean` flag for any field you later add to `User`, with zero extra work.

## Mapping modifiers - adding and removing `readonly` and `?`

A mapped type can do more than copy properties - it can change their *modifiers*. The two modifiers are `readonly` (can't reassign) and `?` (optional). You add one by writing it, and - the part most people don't know - remove one with a `-` prefix.

📝 **Mapping modifiers** - inside `[K in keyof T]`, write `readonly` or `?` to add that modifier to every property, or `-readonly` / `-?` to strip it. A bare `+` (`+readonly`) is allowed but is the default, so rarely written.

This is how `Required<T>` works: strips the optional modifier off everything.

```typescript
// TypeScript's actual Required<T>
type Required<T> = {
  [K in keyof T]-?: T[K];   // -? removes "optional" from every property
};

// And a Mutable<T> - the inverse of Readonly, not built in
type Mutable<T> = {
  -readonly [K in keyof T]: T[K];   // -readonly strips "readonly"
};

type FrozenUser = Readonly<User>;     // all readonly
type ThawedUser = Mutable<FrozenUser>; // readonly stripped back off
```

`Required<T>` mapped over every key and applied `-?`, subtracting the optional modifier - so even if `T` had optional fields, the result has none. `Mutable<T>` did the same trick with `-readonly`, peeling `readonly` off each property; feeding it `FrozenUser` produced a fully writable type again. There's no built-in `Mutable`, so this is genuinely useful to keep around. The `-` prefix is the only way to *remove* a modifier a type already has.

## Key remapping with `as` - renaming the keys themselves

The last move: a mapped type can rename keys as it goes, using an `as` clause. Combined with template literal types (next phase's topic), this lets you transform `name` into `getName`. A `Getters<T>` that turns every field into a getter method:

```typescript
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type UserGetters = Getters<User>;
// {
//   getId: () => number;
//   getName: () => string;
//   getEmail: () => string;
// }
```

The `as` clause rewrites each key before it lands in the result. For key `K`, the template `` `get${Capitalize<string & K>}` `` builds a new name - `id` becomes `getId`, `name` becomes `getName`. The value type became `() => T[K]`, a function returning the original property's type. `Getters<User>` derived a full interface of getter methods straight from `User`'s fields, staying in sync forever. (Don't worry about the template-literal mechanics yet - that's Phase 11; this is a taste of where these pieces lead.)

⚠️ **Keep derived types readable.** This is genuinely powerful, and that's exactly the danger. A type expression three transformations deep can become unreadable - the next person (or future-you) opens `Getters<Omit<Mutable<User>, "id">>` and has no idea what shape comes out. Derivation earns its keep when it removes real duplication and the result is obvious. When the expression itself becomes the puzzle, a plain hand-written type - even with a little duplication - often serves the team better. Cleverness in types has the same cost as cleverness in code: someone has to read it later.

## Recap

1. **`keyof T`** gives the union of a type's keys, and **`T["key"]`** (indexed access) looks up a property's type - the building blocks for everything else in this phase.
2. **Built-in utility types** derive new shapes for free: `Partial<T>` (update payloads), `Required<T>`, `Readonly<T>`, `Pick<T, K>` and `Omit<T, K>` (subsets), and `Record<K, V>` (lookups). They track the source type automatically, killing the "changed `User`, forgot `UserUpdate`" bug class.
3. **Mapped types** (`{ [K in keyof T]: ... }`) are the engine underneath - a compile-time loop over a type's keys. `Partial<T>` is itself just `{ [K in keyof T]?: T[K] }`, and you can write your own derivations the same way.
4. **Mapping modifiers** add or remove `readonly` and `?`. The `-` prefix *removes* a modifier (`-?` powers `Required<T>`, `-readonly` powers a custom `Mutable<T>`) - the only way to strip one a type already has.
5. **Key remapping with `as`** renames keys during the mapping (e.g. a `Getters<T>` mapping `name` → `getName`), pairing with template literal types.
6. ⚠️ Derive to remove duplication, not to show off. When a type expression gets unreadable, a hand-written type may serve the team better.

## Quick check

Lock in what the building blocks do, where the utility types come from, and how to remove a modifier:

```quiz
[
  {
    "q": "Given `interface User { id: number; name: string }`, what is the type `keyof User`?",
    "choices": [
      "The union `\"id\" | \"name\"`",
      "The union `number | string`",
      "An array `[\"id\", \"name\"]` available at runtime",
      "The type `User` itself, unchanged"
    ],
    "answer": 0,
    "explain": "`keyof T` produces the union of a type's *property names* as string-literal types - here `\"id\" | \"name\"`. (The union of property *value* types, `number | string`, is what you'd get from indexed access like `User[keyof User]`.)"
  },
  {
    "q": "TypeScript's `Partial<T>` is defined as a mapped type. Which definition is correct?",
    "choices": [
      "`{ [K in keyof T]?: T[K] }` - iterate the keys and make each property optional",
      "`{ [K in keyof T]-?: T[K] }` - iterate the keys and make each property required",
      "`Pick<T, keyof T>` - pick every key from T",
      "`{ readonly [K in keyof T]: T[K] }` - iterate the keys and make each readonly"
    ],
    "answer": 0,
    "explain": "`Partial<T>` maps over `keyof T` and applies the `?` modifier to every property, giving `{ [K in keyof T]?: T[K] }`. The `-?` version is `Required<T>` (it *removes* optional), and the `readonly` version is `Readonly<T>`."
  },
  {
    "q": "You want a `Mutable<T>` that strips `readonly` off every property. What goes in the mapped type?",
    "choices": [
      "`-readonly [K in keyof T]: T[K]` - the `-` prefix removes the readonly modifier",
      "`readonly [K in keyof T]: T[K]` - writing readonly toggles it off",
      "`[K in keyof T]-readonly: T[K]` - the modifier goes after the brackets",
      "There's no way to remove readonly; you must rebuild the type by hand"
    ],
    "answer": 0,
    "explain": "Prefixing a modifier with `-` removes it: `-readonly [K in keyof T]: T[K]` strips `readonly` from every property. (Writing plain `readonly` *adds* it, and `?`/`-?` work the same way for the optional modifier.)"
  }
]
```


---

# Conditional & Template Literal Types - Types That Make Decisions

This is the deep end of the type system - the phase where types stop being static labels and start to *compute*. If you've ever opened a library's `.d.ts` file, seen `T extends (...args: any[]) => infer R ? R : never`, and quietly closed the tab, this phase is for you. By the end you'll be able to *read* that line, and write its simpler cousins yourself.

Here's the one mental model to carry through everything below: **a type can be computed from another type**. Up to now your types have been fixed shapes - `string`, `Product`, `User[]`. But TypeScript also lets a type *branch* ("if the input is a string, the result is X, otherwise Y") and *pattern-match* ("if the input is a function, pull out its return type"). Conditional types are the branching; `infer` is the pattern-matching; template literal types do the same trick for strings. Everything in this phase is one of those three ideas.

One reassurance up front: **most application code never needs to *write* any of this.** You'll mostly *read* it, in the type definitions of libraries you use. The last section covers exactly when reaching for these tools pays off - and when it makes your code worse.

## Conditional types - a ternary for types

You already know the JavaScript ternary: `condition ? a : b`. Conditional types are the same shape, operating on *types* instead of values.

📝 **Conditional type** - `T extends U ? X : Y`. Read: "if `T` is assignable to `U`, the result is `X`; otherwise `Y`." The `extends` here doesn't mean inheritance - it's a yes/no question: *does `T` fit into `U`?*

Start with the simplest example, which does nothing useful but makes the mechanics obvious:

```typescript
type IsString<T> = T extends string ? true : false;

type A = IsString<"hello">; // true
type B = IsString<42>;      // false
```

`IsString<T>` takes another type `T` as input (that's what `<T>` is - a type parameter, like a function argument but for types). Asking for `IsString<"hello">`, TypeScript checks "is `"hello"` assignable to `string`?" - yes - so the result is `true`. For `IsString<42>`, `42` is not a string, so you get `false`. The type literally *decided* its own value from its input.

Now one you've already used without knowing how it's built. The standard library's `NonNullable<T>` strips `null` and `undefined` out of a type. A conditional-type version of it (the modern standard library uses a shorter `T & {}` trick, but this spells out the same logic):

```typescript
type MyNonNullable<T> = T extends null | undefined ? never : T;

type Cleaned = MyNonNullable<string | null | undefined>; // string
```

For each member of the input, the conditional asks "is this `null` or `undefined`?" If yes, it resolves to `never` - the type with no values, which vanishes from a union. If no, it keeps the type as-is. Feed it `string | null | undefined` and the `null`/`undefined` arms disappear, leaving `string`. (Why it processes each union member separately is covered in the *distributive* section below.)

💡 **`never` is the type-level "delete" button.** When you want a conditional type to *remove* something, resolve that branch to `never`. In a union, `never` evaporates. This pattern - `... ? never : T` - is how almost every "filter out X" utility type is built.

## `infer` - reaching inside a type

A conditional type can ask "does `T` match this shape?" But often you want more than yes/no - you want to *grab a piece* of the matched type. That's what `infer` is for.

📝 **`infer`** - used only inside the `extends` clause of a conditional type. It captures part of the matched type and binds it to a name you use in the `true` branch. A placeholder that says "match anything here, and call it `R`."

The classic example is extracting a function's return type - a simplified version of the built-in `ReturnType<T>`:

```typescript
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function getUser() {
  return { id: 1, name: "Ada" };
}

type User = MyReturnType<typeof getUser>; // { id: number; name: string }
```

The conditional asks "does `T` match the shape *some function returning something*?" `infer R` sits in the return-type position, meaning "capture whatever the return type is as `R`." When `T` is the type of `getUser`, the match succeeds and `R` becomes `{ id: number; name: string }`, which the `true` branch returns. If `T` weren't a function, the match would fail and you'd get `never`. (`typeof getUser` grabs the *type* of the function value - covered in [Phase 5](05-unions-and-narrowing.md).)

The same trick pulls out *parameter* types - exactly how the built-in `Parameters<T>` works:

```typescript
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;

function greet(name: string, times: number) {}

type Args = MyParameters<typeof greet>; // [name: string, times: number]
```

This time `infer P` sits in the *arguments* position, capturing the whole parameter list as a tuple `[string, number]`. Same mechanism, different slot. You don't need to memorize these - TypeScript ships `ReturnType` and `Parameters` built in - but now you can *read* them when you hover over them in your editor.

💡 **`infer` is how library types "reach inside" your types.** Whenever a utility seems to magically know the return type, element type, or resolved value of your `Promise`, there's an `infer` doing the reaching. It's the single most common ingredient in advanced library typings - recognizing it demystifies most of them at a glance.

## Distributive conditional types - the surprising part

Here's the behavior that catches everyone off guard, including veterans. When the type you pass to a conditional is a **union**, the conditional doesn't run once on the whole union - it runs *separately on each member* and combines the results back into a union.

⚠️ **This is called *distribution*, and it's automatic.** A "naked" type parameter (`T` bare on the left of `extends`) distributes over unions. It's why `MyNonNullable<string | null>` worked member-by-member earlier instead of asking "is the whole union `null | undefined`?" - which would have answered "no" and broken everything.

Watch it with a conditional that wraps each type in an array:

```typescript
type ToArray<T> = T extends any ? T[] : never;

type Result = ToArray<string | number>; // string[] | number[]
```

You might have expected `(string | number)[]` - one array of mixed values. Instead you got `string[] | number[]` - *either* an array of strings *or* numbers. That's distribution: TypeScript split `string | number` into `string` and `number`, ran `ToArray` on each, and rejoined the results with `|`. The conditional fired twice, once per union member.

This is usually what you want (it's why filtering utilities work), but when it isn't, suppress it by wrapping both sides in a tuple so `T` is no longer "naked":

```typescript
// [T] is not a naked type parameter, so distribution is off
type ToArrayNoDistribute<T> = [T] extends [any] ? T[] : never;

type Result2 = ToArrayNoDistribute<string | number>; // (string | number)[]
```

Writing `[T] extends [any]` instead of `T extends any` wraps the parameter in a one-element tuple. TypeScript now sees the whole union as a single unit, doesn't split it, and you get the combined `(string | number)[]`. You don't need this often - but when a conditional type gives a weirdly *split* result you didn't expect, distribution is the culprit, and `[T]` is the fix.

## Template literal types - building string types from patterns

Conditional types branch on types. Template literal types do something different: build new *string literal types* by stitching together pieces, using the same backtick syntax as JavaScript template strings.

📝 **Template literal type** - a string literal type built from a pattern, e.g. `` `on${string}` ``. Interpolate other types into a string template, and the result describes strings matching that shape. Combined with union types, one template can describe a whole family of valid strings.

The headline use is generating related string types instead of writing them by hand. Say you have a set of event names and want the "handler" versions (`click` → `onClick`):

```typescript
type EventName = "click" | "focus" | "blur";

type HandlerName = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"
```

The template `` `on${Capitalize<EventName>}` `` interpolated each member of the `EventName` union into the pattern. `Capitalize<T>` uppercases the first letter, so `"click"` became `"Click"`, then the `on` prefix gave `"onClick"`. Because `EventName` is a union, you got a union back: all three handler names, generated automatically. Change `EventName` and the handler names update with it - no manual list to keep in sync.

A more real-world flavor: typed API routes, where a string must follow `METHOD /path`:

```typescript
type Method = "GET" | "POST";
type Path = "/users" | "/posts";

type Route = `${Method} ${Path}`;
// "GET /users" | "GET /posts" | "POST /users" | "POST /posts"

const valid: Route = "POST /users"; // ok
```

```console
const bad: Route = "DELETE /users";
//    ~~~ Type '"DELETE /users"' is not assignable to type 'Route'.
```

The template combined every `Method` with every `Path` - TypeScript takes the cross-product of the two unions, giving all four valid route strings. `"POST /users"` is fine, a member of that union; `"DELETE /users"` is rejected because `DELETE` was never in `Method`. You've turned a free-form string into a tightly checked set, and your editor autocompletes the valid routes as you type - the whole appeal.

## When to reach for this - and when not

Now the straight-talk part. Everything above is powerful, and that power is a trap if you misjudge when to use it.

💡 **You will read these far more than you write them.** The vast majority of application code - components, API handlers, business logic - is typed perfectly well with tools from earlier phases: interfaces, unions, generics, the built-in utility types. Conditional and template literal types live mostly in the `.d.ts` files of *libraries*, written once by authors so thousands of callers get great autocomplete and safety. Reading them is the daily skill; writing them is the occasional one.

⚠️ **Type-level programming can become write-only code, and it can slow your compiler to a crawl.** A deeply nested conditional type with three `infer`s and a recursive helper is genuinely hard for the next person (often future-you) to understand, and elaborate type computations make the compiler and editor sluggish. Before building one, ask: *is the payoff a real, measurable improvement to the people calling this code?* If the answer is "it'd be kind of clever," write the simpler, more verbose type instead. A type you can read at a glance beats a brilliant one you have to decode.

So when *is* it worth it? When you're building something whose entire value is a great typed API for its callers - a query builder, a router, a form library. When Prisma gives fully-typed results matching the columns you selected, or tRPC autocompletes your server procedures on the client with zero code generation, *this is the machinery doing it*: conditional types branching on your schema, `infer` reaching into your function signatures, template literals assembling route strings. It was never magic - it's the three tools you just learned, applied with care.

## Recap

1. A **conditional type** `T extends U ? X : Y` is a ternary for types: checks whether `T` is assignable to `U` and resolves to one branch or the other. Resolving a branch to `never` is how "filter out X" utilities like `NonNullable` delete types from a union.
2. **`infer`** captures a piece of the matched type inside the `extends` clause - it's how `ReturnType` grabs a function's return type and `Parameters` grabs its argument list. Whenever a library type "reaches inside" yours, an `infer` is doing it.
3. **Distributive conditional types**: a conditional over a *union* runs once per member and rejoins the results. Usually what you want, surprises you when it isn't, and is suppressed by wrapping the parameter in a tuple (`[T] extends [U]`).
4. **Template literal types** build string literal types from patterns with backtick syntax (`` `on${Capitalize<E>}` ``), and crossed with unions they generate whole families of valid strings - great for event names, route strings, and the like.
5. You'll mostly **read** these in library `.d.ts` files, not write them - everyday app code rarely needs them.
6. ⚠️ Type-level programming can become unreadable and slow the compiler. Reach for it only when the payoff - excellent autocomplete and safety for callers - is real; otherwise prefer the simpler type. It's the machinery behind the "magic" in libraries like Prisma and tRPC.

## Quick check

Lock in the core moves - branching, reaching inside, and building strings:

```quiz
[
  {
    "q": "What does the conditional type `T extends string ? true : false` resolve to when `T` is `42`?",
    "choices": [
      "`false` - because the number `42` is not assignable to `string`, so the conditional takes the else branch",
      "`true` - because every type extends `string` in TypeScript",
      "`never` - because the types don't match",
      "A compile error, because you can't compare a number to a string"
    ],
    "answer": 0,
    "explain": "A conditional type is a type-level ternary. `extends` asks 'is `T` assignable to `string`?' For `T = 42` the answer is no, so it resolves to the else branch - the type `false`."
  },
  {
    "q": "In `type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never`, what is the role of `infer R`?",
    "choices": [
      "It captures the function's return type and binds it to `R`, so the `true` branch can return that captured type",
      "It declares a new generic parameter that the caller must supply",
      "It forces `T` to be a function or the type errors",
      "It runs the function `T` and stores the result in `R`"
    ],
    "answer": 0,
    "explain": "`infer` is pattern-matching inside the `extends` clause. `infer R` sits in the return-type position and captures whatever the function returns, naming it `R` so the `true` branch can resolve to it. If `T` isn't a function, the match fails and you get `never`."
  },
  {
    "q": "Given `type ToArray<T> = T extends any ? T[] : never`, what is `ToArray<string | number>`?",
    "choices": [
      "`string[] | number[]` - the conditional distributes over each union member and rejoins the results",
      "`(string | number)[]` - one array holding both types",
      "`never` - because a union can't extend `any`",
      "`any[]` - because the condition is `extends any`"
    ],
    "answer": 0,
    "explain": "This is distribution: a naked type parameter over a union runs the conditional once per member, giving `string[]` and `number[]`, then rejoins them as `string[] | number[]`. To get `(string | number)[]` instead, you'd suppress distribution with `[T] extends [any]`."
  }
]
```


---

# Typing the Real World - Libraries, Declarations & Untyped Data

Everything you've built so far has been airtight *inside* your own code. The compiler knows the shape of every value, narrows your unions, infers your generics, and underlines mistakes before you run them. It feels like a fortress.

Here's the mental model for this phase: **the fortress has gates, and the world outside doesn't speak your type system.** A library was written by someone else. A network response is a stream of bytes the compiler has never seen. `JSON.parse` hands you back whatever was in a string at runtime. At every boundary, TypeScript's knowledge runs out - and what it does *instead* of admitting that is the single biggest source of "but the types said it was fine!" bugs in real TypeScript code.

This phase is about those boundaries: how types get attached to other people's code, and what happens (spoiler: nothing good, unless you act) when typed data arrives from outside your program.

## Third-party libraries and `@types`

You install a library with `npm`, import it, and start calling its functions. Where do the types come from? One of two places.

**Many modern libraries ship their own types** - the author wrote the library *in* TypeScript, or hand-wrote type declarations and bundled them in the package. Install it and the editor lights up with autocomplete and signatures. Nothing extra to do.

**Older or JS-only libraries ship no types.** For those, the community maintains a giant separate repository of type declarations.

📝 **DefinitelyTyped** - a massive open-source repository (`github.com/DefinitelyTyped/DefinitelyTyped`) holding hand-written type declarations for thousands of JavaScript libraries that don't ship their own. Each is published to npm under the `@types/` scope, so the types for `lodash` live in `@types/lodash`.

You install those declarations as a dev dependency, alongside the real library:

```bash
npm install lodash
npm install --save-dev @types/lodash
```

For the Node.js built-ins (`fs`, `path`, `process`, and friends), the declarations live in `@types/node`:

```bash
npm install --save-dev @types/node
```

The first command installs `lodash`, the actual runtime code. The second installs `@types/lodash` into `devDependencies`, since type declarations are erased at compile time and never ship to production - a *development*-only need. Once both are present, TypeScript automatically finds the declarations inside `node_modules/@types/` (and any `types` field a package declares in its own `package.json`) without you importing anything. Type `_.` and the editor offers every lodash function with full signatures.

💡 **How the editor "just knows."** You never import a `@types` package. TypeScript scans `node_modules/@types/` on its own and merges those declarations into your project's view of the world - why adding `@types/lodash` instantly fixes the red underline under `import _ from "lodash"`, even though your import points at the real library.

⚠️ **A red underline on an import usually means missing types, not a missing library.** If `import` works at runtime but the editor complains *"Could not find a declaration file for module 'foo'"*, the library is installed but its types aren't. The fix is almost always `npm i -D @types/foo` - if no such package exists, you're in the next section's territory.

## Declaration files (`.d.ts`)

The things inside `@types/` packages are **declaration files**, worth understanding because occasionally you'll write one yourself.

📝 **Declaration file (`.d.ts`)** - a file that describes the *types* of some JavaScript code without containing any implementation. No function bodies, no logic - only signatures and shapes. A contract that tells the compiler "here's what exists and what type it is," while the actual running code lives elsewhere (in plain `.js`).

The keyword that makes this possible is `declare`: it tells the compiler "trust me, this thing exists at runtime - here's its type" without providing or expecting an implementation.

```typescript
// globals.d.ts - describing things that exist at runtime but TS can't see
declare const APP_VERSION: string;
declare function trackEvent(name: string, data: object): void;
```

`declare const APP_VERSION: string` tells TypeScript a global `APP_VERSION` exists and is a string - perhaps injected by your build tool at compile time. There's no value assigned, since this file produces no runtime code; it's pure description. Now `APP_VERSION.toUpperCase()` type-checks everywhere in your project, and the compiler trusts the real value will be there at runtime.

The most common reason *you'd* write one is silencing the "no declaration file" error for an untyped module with no `@types` package. Stub it with `declare module`:

```typescript
// untyped-modules.d.ts
declare module "legacy-chart-lib" {
  export function render(el: HTMLElement, data: number[]): void;
}
```

`declare module "legacy-chart-lib"` defines the type contract for an import that otherwise has none. Now `import { render } from "legacy-chart-lib"` resolves, and `render` has a real signature instead of falling back to `any`. You're describing only the slice of the library you actually use. Mostly, though, you *consume* declaration files written by others; authoring them is rare.

## The danger zone: data from outside

Now the part that catches everyone. Inside your code the compiler verifies everything; the moment data crosses a gate from outside, that verification silently stops, replaced by blind trust.

⚠️ **`JSON.parse()` returns `any`. And `await response.json()` from `fetch` returns `Promise<any>`.** That `any` is the compiler waving a white flag: it has no idea what's in that string or response, so it surrenders all checking. Whatever type you *claim* the result is, TypeScript will believe you completely, with no verification - even if the server sent back something entirely different.

Watch the trap spring:

```typescript
interface User {
  id: number;
  name: string;
  email: string;
}

async function loadUser(id: number): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  const user: User = await response.json(); // looks safe. is not.
  return user;
}

const u = await loadUser(1);
console.log(u.email.toLowerCase()); // compiles cleanly
```

`response.json()` is typed `Promise<any>`. Annotating `const user: User` tells the compiler "this is a `User`," and since the source was `any`, it accepts the claim without evidence. Every line that follows is type-checked *against your claim*, not reality - `u.email.toLowerCase()` compiles perfectly.

But suppose the server is having a bad day and returns `{ "error": "not found" }`. There's no `email` field. At runtime, `u.email` is `undefined`, and `undefined.toLowerCase()` throws:

```console
TypeError: Cannot read properties of undefined (reading 'toLowerCase')
```

The annotation `: User` did *nothing* at runtime - a compile-time fiction, a label you stuck on untyped data. The types describe what you *hope* arrives, and the compiler can't tell hope from fact for anything that comes from outside.

💡 **Types are a contract you write; the outside world never signed it.** Inside your program, both sides of every assignment are checked, so the contract holds. For a network response, *you* wrote the type and *you* alone are bound by it - the server has no idea your `User` interface exists and is free to send whatever it likes. This is the gap every robust TypeScript app has to close deliberately.

## Type assertions (`as`) - a promise, not a check

The annotation trap above is closely related to a feature you'll see (and be tempted by) constantly: the type assertion.

📝 **Type assertion (`value as Type`)** - tells the compiler "treat this value as `Type`, trust me." It performs *no* runtime check and changes *no* runtime behavior, only overriding what the compiler thinks the type is. A promise from you to the checker, enforced by nobody.

This is fundamentally different from **narrowing**, which you learned earlier. Narrowing (`if (typeof x === "string")`, `if ("email" in obj)`) *proves* a type with a real runtime test the compiler can see. An assertion *asserts* a type with no test at all:

```typescript
const raw: unknown = JSON.parse(input);

// Narrowing - verified at runtime, safe:
if (typeof raw === "object" && raw !== null && "name" in raw) {
  // compiler knows raw has a `name` here because the code checked
}

// Assertion - unverified, you take responsibility:
const user = raw as User; // no check happens. ever.
```

The narrowing branch runs an actual `if` that exists in the compiled JavaScript - the check happens at runtime, so the compiler's belief is backed by evidence. The assertion `raw as User` compiles to *nothing*; `as User` vanishes entirely in the emitted JS. You've told the checker `raw` is a `User`, it stops worrying, and if `raw` is actually `{ error: "..." }`, you've moved the crash downstream to wherever the missing field gets touched.

TypeScript blocks an assertion between two *clearly* unrelated types (e.g. `string as number`). The escape hatch is the double assertion through `unknown`:

```typescript
const sketchy = someValue as unknown as User;
```

Routing through `unknown` first tells the compiler "forget what you knew about this value's type," then re-asserts it as `User`. It's the strongest "trust me" you can write, and the loudest alarm bell in a code review. Every `as unknown as` fully disables the type checker for that value and bets the program's correctness on your assumption being right.

⚠️ **An assertion is where you take responsibility *away* from the checker.** Each `as` is a spot the compiler is no longer protecting - it does what you said instead of what it verified. They have legitimate uses (telling the compiler something it genuinely can't infer), but every one is a small loan against safety. Use them sparingly, and never reach for `as` to make external data "be" a type - that's not closing the gap, it's papering over it.

## Closing the gap: runtime validation

So what *does* close the gap? If types can't verify external data and assertions just lie about it, the only real answer is to check the data yourself, at runtime, the moment it arrives - and derive the static type from that check so the two can never drift apart.

The hand-rolled version is a **type guard**: a function that inspects an `unknown` value and returns a special boolean narrowing the type for the compiler.

```typescript
interface User {
  id: number;
  name: string;
  email: string;
}

function isUser(x: unknown): x is User {
  return (
    typeof x === "object" &&
    x !== null &&
    typeof (x as Record<string, unknown>).id === "number" &&
    typeof (x as Record<string, unknown>).name === "string" &&
    typeof (x as Record<string, unknown>).email === "string"
  );
}

async function loadUser(id: number): Promise<User> {
  const data: unknown = await (await fetch(`/api/users/${id}`)).json();
  if (!isUser(data)) {
    throw new Error("API returned a shape that isn't a User");
  }
  return data; // narrowed to User - and actually checked
}
```

`isUser` returns `x is User` - a **type predicate**. When it returns `true`, the compiler narrows the argument to `User`, exactly like `typeof` narrowing. But unlike an assertion, the narrowing is *earned*: the function genuinely inspected every field at runtime. `loadUser` types the response as `unknown`, forcing itself to validate before using it. If the server lies, `isUser` returns `false`, you throw at the boundary, and the bad data never reaches your core logic - the crash, if any, happens *at the gate* with a clear message, not three screens away with a cryptic one.

Writing those guards by hand is correct but tedious, and they drift from your interface the instant someone adds a field. The popular fix is a validation library - **zod** is the one you'll meet most - letting you define the shape *once* for both a runtime validator and a static type.

```typescript
import { z } from "zod";

// Define the shape once:
const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.email(),
});

// Derive the static type from the schema - single source of truth:
type User = z.infer<typeof UserSchema>;

async function loadUser(id: number): Promise<User> {
  const data: unknown = await (await fetch(`/api/users/${id}`)).json();
  return UserSchema.parse(data); // validates at runtime; throws on mismatch
}
```

`UserSchema` describes the shape as a runtime *value* that knows how to check data. `UserSchema.parse(data)` actually inspects the object at runtime and throws a detailed error if anything is wrong (missing field, wrong type, malformed email) - real verification, unlike `as`. The magic line is `type User = z.infer<typeof UserSchema>`: it pulls a static type *out of* the schema, so your compile-time type and runtime check come from one definition and can never disagree. `parse` returns a value the compiler knows is `User` - and this time it's right, because the data was genuinely checked.

💡 **Validate at the edges, trust types in the core.** This is the durable pattern for real TypeScript. At every boundary where data enters - network responses, form input, `localStorage`, environment variables, message queues - validate it once and convert `unknown` into a known type. Everywhere *inside* that boundary, lean fully on the type system: it's accurate now, because nothing untyped got past the gate. The fortress works again - you just had to put guards on the doors.

## Recap

1. Libraries get their types either by **shipping their own** or via **`@types/` packages from DefinitelyTyped** (`npm i -D @types/foo`); the editor finds them automatically in `node_modules/@types/` - you never import them.
2. A **declaration file (`.d.ts`)** describes types without implementation; `declare` asserts something exists at runtime, and `declare module "foo"` stubs an untyped library. You mostly consume these, rarely write them.
3. ⚠️ External data is the danger zone: `JSON.parse()` is `any` and `fetch().json()` is `Promise<any>`. TypeScript believes whatever type you claim for it - the annotation is a **compile-time fiction** with zero runtime verification.
4. A **type assertion (`as`)** is a promise, not a check - it changes no runtime behavior and takes responsibility away from the compiler. `as unknown as T` is the double-assertion escape hatch. Contrast with **narrowing**, verified by a real runtime test.
5. The real fix is **runtime validation at the boundary**: a hand-written **type guard** (`x is User`) or a library like **zod**, where `z.infer` derives the static type from the validator so check and type stay one source of truth.
6. 💡 **Validate at the edges, trust types in the core** - turn `unknown` into a known type once at every entry point, then rely on the type system everywhere inside.

## Quick check

Test yourself on the gap between what types promise and what gets checked:

```quiz
[
  {
    "q": "You write `const user: User = await response.json()` and `user.email.toLowerCase()` compiles with no errors. The server returns `{ error: \"not found\" }`. What happens?",
    "choices": [
      "It crashes at runtime with a TypeError - the `: User` annotation did nothing, because `response.json()` is `any` and TypeScript trusted your claim without checking",
      "The compiler catches it before running, since it knows the server's real response shape",
      "It returns `undefined` silently and the program continues safely",
      "`response.json()` automatically validates the data against the `User` interface at runtime"
    ],
    "answer": 0,
    "explain": "`response.json()` returns `Promise<any>`, so annotating the result as `User` is an unverified claim. The types are a compile-time fiction for external data - at runtime `user.email` is `undefined` and `.toLowerCase()` throws."
  },
  {
    "q": "What is the key difference between `raw as User` (assertion) and an `if` that checks the fields (narrowing)?",
    "choices": [
      "The assertion performs no runtime check and compiles to nothing; narrowing runs a real test the compiler can see, so its conclusion is backed by evidence",
      "Narrowing is slower because it adds runtime code; the assertion is the faster, safer choice",
      "They are identical - `as` is just shorthand for an `if` check",
      "The assertion validates the data at runtime while narrowing only affects the editor"
    ],
    "answer": 0,
    "explain": "`as` is a promise to the compiler with no runtime check - it vanishes in the emitted JS. Narrowing proves a type with an actual runtime test the compiler can observe, so the type it infers is earned, not assumed."
  },
  {
    "q": "Why is defining a `zod` schema and using `type User = z.infer<typeof UserSchema>` better than writing the `User` interface and a separate hand-rolled type guard?",
    "choices": [
      "The runtime validator and the static type come from one definition, so they can never drift apart - change the schema and the type updates automatically",
      "zod skips runtime checks entirely, which makes it faster than a type guard",
      "zod lets you use `as` assertions safely without any validation",
      "Interfaces can't describe network data, but zod schemas can"
    ],
    "answer": 0,
    "explain": "With a hand-written interface plus a separate guard, the two can fall out of sync when fields change. `z.infer` derives the static type from the same schema that does the runtime validation, making them a single source of truth that stays consistent."
  }
]
```


---

# Where to Go Next - Putting TypeScript to Work

You made it. You can read and write generics, model real data with unions and discriminated unions, narrow types until the compiler trusts you, and bend the type system with conditional and mapped types. That's the *hard* part, and the durable one - frameworks come and go, but the type system you just learned is the same whether you're writing a React component, a server, or a build script.

So this last phase isn't more syntax. Everything from here is **applying** what you already know. TypeScript on its own is rarely the destination - it's the language you reach for *while* building something else. This is the clear map of where it goes, and what to build so it sticks.

## The branches from here

```mermaid
flowchart TD
  You[You: solid TypeScript] --> FE[Frontend: React + TS]
  You --> BE[Backend: Node + TS]
  FE --> FS[Full-stack: end-to-end types]
  BE --> FS
```

*What this shows:* two directions lead out - browser and server - converging on what makes TypeScript genuinely special: types that flow across the whole stack. You don't have to pick forever, but pick **one to go deep on next**. Depth beats breadth when learning; a half-understood frontend plus a half-understood backend is worse than one you actually command.

## Frontend with React + TypeScript

This is the most common TypeScript job, full stop. React describes a UI as components, and TypeScript types the data flowing through them: the `props` a component accepts, the shape of its `state`, the return of a `useState` or custom hook. The payoff you felt all through this guide - autocomplete on every property, a red squiggle the instant you pass the wrong shape - is what makes typed React pleasant. You stop guessing what a component expects; the types tell you.

If you came from our [JavaScript guide](/guides/javascript-from-zero) and met React there, this is the same React with a safety net bolted on - a short leap.

## Backend with Node + TypeScript

The same language runs the server. With **Node**, you build typed APIs - code that listens for requests, talks to a database, and sends back JSON. The popular starting points are **Express** (small and everywhere), **Fastify** (faster, schema-friendly), and **NestJS** (opinionated, structured, heavy on the TypeScript). Here the types guard a different boundary: the request body, the database row, the response shape. A typo in a field name becomes a compile error instead of a 3 a.m. production page.

The natural path if you liked modeling data more than rendering it.

## Full-stack and end-to-end type safety

Here's where it gets good. Combine a typed frontend, a typed backend, and a database, and you can make **one type definition flow across all three**. Tools like **tRPC** and **Prisma** are built on exactly the mapped and conditional types from [Phase 10](10-utility-and-mapped-types.md) and [Phase 11](11-conditional-and-template-types.md): Prisma generates types from your database schema, tRPC carries your server's function signatures to the client untouched. The result is autocomplete from the database row all the way to the button in the UI - no hand-written API contract in between.

> 💡 This is *the* reason TypeScript is everywhere. One type definition can travel from database → server → client. Rename a column or change an API's return shape, and the frontend lights up with red squiggles **before you ship** - the compiler catches the mismatch across the entire stack, in your editor, the moment you make it. No other mainstream stack gives you that for free.

Don't try to learn tRPC and Prisma cold, though. Build a small typed frontend and backend separately first; full-stack type safety makes sense only once you've felt both halves.

## What to actually build

Reading got you here. *Building* turns knowledge into skill - something small enough to finish but real enough to teach you the messy parts. In rough order:

1. **Convert a small JS project to TS.** Take something you (or anyone) already wrote in JavaScript, rename the files, turn on `strict`, and fix the errors one by one. The fastest way to *feel* the payoff - every error the compiler surfaces is a bug it would have caught for you.
2. **A typed API plus a typed frontend that consumes it.** Start with a plain REST API (Express or Fastify) and a small frontend that fetches from it. When comfortable, rebuild the seam with **tRPC** and watch the types flow across it.
3. **A CLI with typed arguments.** Smaller and underrated - a command-line tool that parses and validates its flags. Great for practicing unions, narrowing, and modeling input without a browser in sight.

Whatever you pick: **finish one.** A finished rough project teaches more than three polished half-projects abandoned at 80%.

## A last word

When a corner of the type system feels fuzzy, the [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html) is the canonical reference - accurate, thorough, and where the experts actually check. Bookmark it.

If the JavaScript *underneath* still feels shaky - closures, `async`/`await`, the event loop - that's worth shoring up, since TypeScript only types the JavaScript you already understand. [JavaScript From Zero](/guides/javascript-from-zero) is right there. For the big-picture view of how languages relate and why TypeScript made the choices it did, [Languages, Explained Like a Human](/guides/languages-explained-like-a-human) is a calm read that puts it all in context.

You started this guide unsure what an `interface` was for. You're leaving it able to model real-world data, reason about generics, and choose your next step on purpose. That's the hard part, and it's behind you. Go build the small thing - the rest is more of what you already know.

## Recap

1. **You learned the durable part - the type system itself.** Everything from here is applying it; frameworks change, but generics, unions, and narrowing carry over everywhere.
2. **Two branches lead out:** typed **React** on the frontend (the most common TS job) and typed **Node** APIs on the backend (Express, Fastify, NestJS). Go deep on one.
3. **End-to-end type safety is TypeScript's killer payoff:** with tRPC and Prisma, one type definition flows from database to UI, so a backend change lights up red squiggles in the frontend before you ship.
4. **Build to learn:** convert a small JS project to TS with `strict` on, build a typed API plus a frontend consuming it, or write a CLI with typed args. **Finish one.**
5. **Keep the [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html) close** as your canonical reference, and shore up the JavaScript underneath if it still feels shaky.

## Quick check

One last check on the big picture:

```quiz
[
  {
    "q": "Why is 'end-to-end type safety' described as TypeScript's killer payoff?",
    "choices": [
      "One type definition can flow from database to server to client, so a backend change surfaces as a compile error in the frontend before you ship",
      "It makes your code run faster at runtime by skipping type checks",
      "It removes the need to ever write a frontend or a database",
      "It automatically writes your React components for you"
    ],
    "answer": 0,
    "explain": "Tools like Prisma and tRPC let a single type definition travel across the whole stack. Change a column or an API's return shape and the mismatch lights up as a red squiggle in the frontend in your editor - caught before it ever reaches production."
  },
  {
    "q": "You want to feel TypeScript's payoff as fast as possible. Which first project does that best?",
    "choices": [
      "Convert a small existing JS project to TS, turn on `strict`, and fix the errors",
      "Read the entire TypeScript Handbook cover to cover before writing any code",
      "Build a large full-stack app with tRPC and Prisma on day one",
      "Rewrite the TypeScript compiler from scratch"
    ],
    "answer": 0,
    "explain": "Converting a real JS project with `strict` on surfaces concrete bugs immediately - every error the compiler flags is one it would have caught for you. It's the quickest way to feel the safety net, and it builds on JavaScript you already understand."
  },
  {
    "q": "Which advanced TypeScript features from this guide power tools like tRPC and Prisma?",
    "choices": [
      "Conditional and mapped types - used to transform and derive types across the stack",
      "Only the `any` type, applied everywhere",
      "Runtime reflection that inspects values while the program runs",
      "Nothing from the type system - they're written in plain JavaScript"
    ],
    "answer": 0,
    "explain": "Prisma generates types from your database schema and tRPC carries your server's signatures to the client - both lean on the mapped and conditional types you met in Phases 10-11 to transform one type into another automatically."
  }
]
```
