Skip to content
teach

Learning: TypeScript

Become the engineer trusted to own a TypeScript codebase on a team: able to model a domain so the compiler rejects the states that should not exist, keep type assertions out of code that carries weight, validate what crosses a runtime boundary rather than assuming the types held, and review someone's types and say concretely why a clever generic is costing more than it gives.

Start here: 0001. Values and Coercion
Latest lesson: 0049. Trusted to Make the Call, which closes the arc

Success looks like

  • Predict what JavaScript does with coercion, closures, this and the event loop, before running the code and without the type system's help.
  • Turn a set of illegal states into a type the compiler rejects, and prove exhaustiveness with never.
  • Read a generic's error message to its cause instead of reaching for as.
  • Say exactly what survives compilation and what erases, and validate every value entering the program from outside it.
  • Configure the compiler with intent: name what each strictness flag buys and why the module and target settings are what they are.
  • Write a generic function whose inference works, so callers annotate nothing.
  • Publish a package whose types are usable by consumers, including the declaration output and the exports it advertises.
  • Review a pull request and say precisely why a conditional type, an assertion, or an enum is the wrong tool there.

Constraints

  • Assumes no prior TypeScript, and no more JavaScript than the arc teaches. TypeScript is a type system over JavaScript, so JavaScript semantics are taught wherever they explain the behaviour a type cannot.
  • The compile-time and runtime boundary is treated as a hard line throughout, because almost every serious TypeScript mistake is a claim about runtime that only the compiler ever checked.
  • Needs only a JavaScript runtime and a terminal on any supported OS, with the online playground standing in for both when a rep only needs to see a type. No paid tooling, no cloud account, no framework.
  • Reps are small programs and small type experiments that fit one sitting.
  • TypeScript releases every few months and the type system gains real expressiveness in those releases. Version-sensitive claims are checked against the release notes, and any lesson that depends on a version says which one.

Out of scope

  • Frameworks and UI libraries as subjects in their own right: React, Angular, Vue, Svelte, Next.js.
  • Bundlers and monorepo tooling as subjects: webpack, Vite, esbuild, Turborepo. Module resolution is taught from the compiler's own model, which is what those tools have to agree with.
  • Other runtimes as subjects: Deno, Bun, the browser platform. They appear only where behaviour genuinely differs.
  • Node.js as a platform in its own right: streams, clustering, native addons.
  • Type-level programming as sport. Mapped and conditional types are taught to the point where they serve a caller, and stage 6 says explicitly where that point is.

The arc

Seven stages, zero to senior. Not a lesson list: a stage takes several lessons, and the boundaries are soft.

Stage Lessons Covers Done when
1. The JavaScript underneath 0001 to 0007 Values and coercion, objects and prototypes, closures, this, modules, the event loop, promises and async Can predict what a program does at runtime, with no types involved
2. Types over values 0008 to 0014 Primitives, arrays and tuples, unions, literal types, structural assignability, narrowing, function types, readonly, what inference already knows Annotates only where inference cannot reach
3. Strictness and the compiler 0015 to 0021 The strict family flag by flag, the strict-shaped flags strict still omits, unknown versus any, assertions versus declarations, tsconfig, module resolution, target and lib Keeps strictness on and fixes what it reports instead of suppressing it, and turns on the checks strict leaves out
4. Modelling 0022 to 0028 Discriminated unions, exhaustiveness with never, branded types, generics and constraints, satisfies, interface versus type alias Illegal states are unrepresentable, and the compiler is what proves it
5. The runtime boundary 0029 to 0035 What erases, declaration files, validating input at the edge, errors as values, consuming untyped and wrongly typed dependencies No value enters the program unvalidated, and no assertion is load-bearing
6. Type-level tools 0036 to 0042 Mapped and conditional types, infer, template literal types, variance and assignability, inference for library APIs, compiler performance Writes an API whose types serve its callers, and stops before cleverness
7. Judgment 0043 to 0049 Publishing a public type surface, what counts as a breaking change in types, review, reading the release notes and the compiler's own declarations Trusted to make the call and to explain it to someone else

Lessons

Work through these in order.

# Lesson Teaches
0001 Values and Coercion Seven primitives, two empties, and an equality operator that converts before comparing
0002 Objects Are References const stops rebinding, spread copies one level, and readonly disappears at run time
0003 Prototypes and Classes Property lookup walks a chain, and class syntax is one way to build that chain
0004 this Is Decided by the Call this comes from how a function is called, so extracting a method throws it away
0005 Scope and Closures A closure captures the binding rather than the value, and let makes one per iteration
0006 The Event Loop, Promises and await Microtasks drain before the next timer, and two awaits in a row are sequential
0007 Modules Imports are hoisted and bound live, and a file with no import or export is not a module
0008 The Types You Write The annotation vocabulary, and a compiler that already objects before you configure it
0009 Tuples and readonly A fixed length the compiler enforces on reads and forgets on push
0010 Unions and Literal Types A value with more than one possible type, and the widening that throws the interesting one away
0011 Structural Assignability Shape decides what goes where, except for the one check that only fires on a fresh object literal
0012 Narrowing Control flow tells the compiler what a union has become, and exactly two things take it back
0013 Function Types Parameters are checked in the direction you expect, unless you wrote a method
0014 What Inference Already Knows Where inference reaches, so that writing an annotation means something when you do
0015 What strict Turns On Seven checks behind one flag, each with a failure it exists to catch
0016 The Checks strict Leaves Out Six more checks worth having, one that the compiler refuses to enable, and one that does nothing at all
0017 unknown Instead of any One type accepts everything and lets you do nothing, the other accepts everything and checks nothing
0018 An Assertion Is Not a Check Every way to tell the compiler to stop checking, and what each one costs when the claim is wrong
0019 Reading a tsconfig Which files the compiler looks at, where the settings come from, and why the generated file says what it says
0020 target and lib One setting changes what is emitted and the other changes what exists, and naming the second throws the default away
0021 Module Resolution The same compiler options give opposite verdicts depending on one field in package.json
0022 Discriminated Unions One shared literal property, and a union the compiler can take apart
0023 Exhaustiveness With never Adding a member to a union should break every switch that ignored it, and one line makes it so
0024 Making Illegal States Unrepresentable Count the states your type permits, then remove the ones your domain does not have
0025 Branded Types Nominal typing on top of a structural system, and the single assertion it costs
0026 Generics and Constraints A type parameter is a promise to the caller, and a constraint is what lets you keep it
0027 satisfies Check a value against a type without letting the type replace what you wrote
0028 interface or type One reports a conflicting member where you wrote it and the other reports it somewhere else
0029 Nothing Survives to Run Time Every type is a claim about a program that no longer exists when the claim would matter
0030 unknown at the Edge Find every place a value arrives from outside, and give each one the only honest type
0031 Parsing Instead of Asserting One declaration that produces both the runtime check and the static type
0032 Type Predicates and Assertion Functions A signature that promises a check, and a body nothing compares it against
0033 Errors as Values A failure in the return type is one the compiler can make you handle
0034 Declaration Files Types for code the compiler never sees, and what that promise is worth
0035 When the Declaration Lies The compiler believes the declaration and the runtime has never heard of it
0036 Mapped Types Transform every property of a type at once, and remove the ones you do not want
0037 Conditional Types A type that branches, and a union it quietly takes apart first
0038 infer Pull a type out of a position instead of asking the caller to name it
0039 Template Literal Types A string pattern the compiler checks, and the key names it can build
0040 Variance and Assignability Which direction a type may travel, and the array that gets it wrong on purpose
0041 Inference for Library APIs Design the signature so the caller annotates nothing
0042 Knowing When to Stop Measure what a clever type costs, then justify it to whoever maintains it
0043 What Counts as a Breaking Change Which edits to a published type break a consumer, and which direction each one has to travel
0044 Publishing a Type Surface What a consumer actually resolves when they import your package, and how to check it before they do
0045 The Consumer's Settings Are Not Yours A strict consumer inherits a loose author's claims and has no way to notice
0046 The TypeScript That Does Not Erase Four features that emit code you did not ask for, and the flag that names them
0047 Settling It From the Source Which document answers a question about the compiler, and how to read the declarations it ships
0048 Reviewing TypeScript Naming what a type costs, instead of saying it looks complicated
0049 Trusted to Make the Call The last judgment in the arc, and what the compiler was never going to do for you

Reference

  • Glossary: canonical terms for this topic
  • Resources: trusted sources, each annotated with what it covers
  • Coercion and equality: falsy values, what == converts, which default operator to write
  • Event loop and promises: queue ordering, combinators, and the async mistakes no compiler reports
  • Types over values: what each annotation buys, where inference reaches, and the holes the compiler leaves open on purpose
  • Strictness and the compiler: which flag catches which failure, where a setting comes from, and how an import resolves
  • Modelling: which shape removes the illegal state, and which declaration catches the mistake where you made it
  • The runtime boundary: where values enter, what checks them, and which claims nothing checks at all
  • Type-level tools: which construct computes which type, and the test a clever one has to pass
  • Judgment: which edits break a consumer, what does not erase, and how to argue the call from the source

How this works

Each lesson is short and self-contained. Answer keys are collapsed: recall first, then open them. The real-world reps matter more than the reading, and spacing them out is the point. Anything still unclear at the end of a lesson is worth chasing to its primary source before moving on.

Table of contents