TypeScript Survival Guide

Course manual

Bearings

Chapter 01 — Bearings: can you run it and can you read it

Scouts checking gear at the trailhead before stepping off

You are at the trailhead. Before you walk, you check bearings: does this machine run code, and can your eyes parse what is on the page. Two checks, two minutes, then you read your first real file. The rest of the chapter — the JavaScript you stand on, why a checked layer earns its keep, the compiler, the editor, the repo habit — hangs off those two checks. Pass them and the first lab compiles. Fail one and you know exactly what to fix before the next paragraph.

Do this now (30 seconds, no setup). Open a terminal and run:

node -v    # need v18 or newer
npx tsc --version

node -v prints v20.x or v22.x = you can run JavaScript outside the browser. npx tsc --version prints Version 5.x = you can check types. If either prints command not found, install the current LTS from https://nodejs.org/en/download, reopen the terminal, run both again. No global installs, no ceremony — the project brings its own tools and npx runs the project’s copy.

Do not skip the second check. Every later drill starts with npx tsc --noEmit — silence is green, first error is the fix. If that command is not answering now, nothing after it can teach you.

Aaliyah is on radio at the kiosk, reading the gate mechanism you just passed to the whole patrol. You will read it with her next — one if, one string, one gate.

Reading code before writing it: characters, spacing, indent

You have run two commands. Now learn to read the page they will check.

A source file is plain text. Every meaningful character is one of a small set you already know from writing sentences:

  • braces {}, brackets [], parens () group things — open, contents, close.
  • colon : labels a value (name: string = name holds text).
  • comma , and semicolon ; separate items and end statements.
  • dot . reaches into a thing (scout.name).
  • quotes " ' ` wrap text; backticks ` with ${} splice values into text.
  • // starts a comment to end of line; /* */ wraps a block. Comments teach, never execute.
  • = assigns; === compares; => builds a short function; ... spreads a copy; ? and ?? guard the missing case (next section).

Spacing and indenting: JavaScript ignores extra spaces, blank lines, and indent — const x=1 and const x = 1 mean the same. We keep them anyway because humans read code with eyes. One space after : and ,, one empty line between ideas, indent two spaces inside {}. Your editor (Prettier, on save) will fix this for you once you switch it on below — until then, mimic the register below character for character.

Read this roster the way Aaliyah does at the trailhead — one record at a time, name, role, crew, badge number, out loud. Then trace the function under it that turns a record into a line of text. Amara and Nizhoni are on Fire crew tonight; the function label builds their trail tags. If you can point at each : , { } in the block below and say what it groups, you can read every example that follows.

The Law on the trail

You just read code out loud. The patrol does the same with the Law: Aaliyah reads five lines and the patrol packs five habits:

  • Honest and fair — the compiler reports what is there, not what you hoped was there.
  • Considerate and caring — name things so the next reader survives the night.
  • Courageous and strong — break the build on purpose in the drills; red is how you learn.
  • Use resources wisely — strict mode spends checking now to save debugging later.
  • A sister to every scout — every drill answer is verifiable, so no one is left guessing.

The tooling below exists to keep those five. When a rule feels fussy, ask which line of the Law it is keeping.

Our running examples use a trail patrol register: names, roles, crews, badge numbers. The trail is the dataset, never the subject — every example teaches a language fact first. Production examples come second, cut from a shipping codebase (the Sumerian Squares 3D stage), so you see the same shapes in the wild after you have met them at home.

The gate you just passed

You entered through a shared word. Here is the whole mechanism — it is the first production code you will read in this course:

// layouts/partials/courtesy-gate.html (simplified)
if (sessionStorage.getItem(GATE_KEY) === SHARED_WORD) {
	showCourse();
} else {
	askForWord();
}

One sessionStorage read, one string comparison, one overlay. That is all. It keeps casual browsers out and nothing more: the word ships in the page bundle, so anyone who opens DevTools can read it. Client-side gates are courtesy, not security — server-side auth happens on a server you control, per request, with secrets the browser never sees. Remember this shape; Chapter 09 (external data) and Chapter 14 (practices) will show you what the serious version looks like, and you will know exactly what it is buying you.

Modern JavaScript: the ES2020+ baseline

TypeScript assumes you can read modern JavaScript. You do not need fluency, but the following features appear in every chapter, so meet them here.

const and let declare bindings; const is the default because most bindings never change. Arrow functions ((s) => s.name) are the short form you will see in every .map and .filter. Template literals (`${s.name} holds badge #${s.badge}`) build strings from values. Destructuring (const { name, role } = scout) pulls fields out of objects. Spread ([...patrol], { ...scout, badge: 3 }) copies arrays and objects instead of mutating them.

Optional chaining (scout?.nickname) reads a property only when the value exists, giving undefined otherwise. Nullish coalescing (nickname ?? name) falls back only on null or undefined, unlike ||, which also falls back on "" and 0. These two operators, both from ES2020, are the JavaScript answer to missing data — TypeScript later makes the compiler enforce the same care.

Modules tie files together: export shares a binding, import receives it. A patrol file exports a Scout type and a patrol array; a screen file imports both. Everything in this course is written as ES modules (import / export), never the older require style.

export interface Scout {
	name: string;
	role: string;
	crew: string;
	badge: number;
}

export const patrol: Scout[] = [
	{ name: "Amara", role: "Lookout", crew: "Fire", badge: 3 },
	{ name: "Nizhoni", role: "Medic", crew: "Fire", badge: 5 },
];

export function label(s: Scout): string {
	return `${s.name} holds badge #${s.badge}`;
}

In the wild: the same shapes run a shipping 3D stage. This interface from the Sumerian Squares host pool labels every pooled canvas host — an imported id type, method signatures, one optional field, and a Map keyed by host:

import type { HostId } from "./types";

export interface PooledHost {
	hostId: HostId;
	cell: HTMLElement;
	canvas: HTMLCanvasElement;
	engine: {
		dispose(): void;
		resize(): void;
		runRenderLoop(fn: () => void): void;
		stopRenderLoop(): void;
	};
	regSeq?: number;
}

const pool = new Map<HostId, PooledHost>();

Read it with Chapter 02 eyes: import type borrows only the label (Chapter 04), regSeq? may be absent (Chapter 08 guards will check), and the Map holds the same name-to-record shape as the patrol array above, indexed for lookup. Production TypeScript is this chapter’s vocabulary at full speed — nothing here is new syntax, only higher stakes.

JavaScript deserves respect here: it runs everywhere, it improved dramatically with ES2020 and beyond, and every TypeScript program is a JavaScript program underneath. Types add checking; they change nothing about the language’s reach.

New here: modules, interfaces, optional chaining, nullish coalescing. Docs: TypeScript for JavaScript Programmers. Watch: 030 — 100+ JavaScript Concepts (Fireship’s 12-minute checklist — pause on every term you cannot define; each pause is a gap this chapter closes), and for the full prerequisite path, 027 — Learn JavaScript Full Course (freeCodeCamp, Bo Karnes — “from how do you run JavaScript at all through basic syntax, data structures, functions, control flow, and into ES6”).

Why TypeScript

JavaScript fails silently. Subtract a number from a string and you get NaN, not an error. Read a property off a missing object and you get a crash at the worst moment — in front of a user. These are not exotic bugs; they are Tuesday.

A type is a label describing what shape a value has: this binding holds a string, that function returns a number, this object always carries a name. The TypeScript compiler checks every label before the code runs. Whole families of crashes become red squiggles in your editor instead of blank pages for your users. The check happens at author time, costs nothing at runtime (types are erased when the code compiles), and documents every function signature for the next reader.

That is the entire pitch, and the rest of the course is its application: label values, let the compiler verify the labels, and handle the places where outside data arrives without one.

New here: static typing, compile-time checking. Docs: TypeScript for JavaScript Programmers. Watch: 079 — TypeScript for Beginners, the Full Hour (Mosh: “Every JavaScript file is a valid TypeScript file” — and with the added static typing “we can catch a lot of our mistakes at compile time” instead of testing every path by hand), then 080 — What TypeScript Is and What to Install (Shaun’s one-slide version: a superset adding strict types, plus compile-down of modern features — tsc emits older JavaScript “which the browsers do understand”). For the motivation reel, 029 — You Don’t Know JavaScript is twenty-three minutes of the silent failures above, dramatized.

tsc, tsconfig, and project structure

tsc is the TypeScript compiler. Installed per project (npm install -D typescript), it is run through npx as npx tsc --noEmit for a pure check — read every file, verify every type, write nothing. Silence is green; any output is a to-do list, and you read only the first error, because later errors are usually fallout from the first.

tsconfig.json is the project’s compiler settings file. A Day 1 config is small and strict:

{
	"compilerOptions": {
		"target": "ES2022",
		"module": "ESNext",
		"moduleResolution": "bundler",
		"strict": true,
		"skipLibCheck": true
	},
	"include": ["src"]
}

target sets the JavaScript version emitted; module and moduleResolution describe how imports resolve; strict switches on the full family of safety checks (leave it on — Chapter 02 shows what each one catches); include scopes the compiler to your source folder.

New here: tsc, tsconfig.json, watch mode. Docs: TypeScript Tooling in 5 Minutes plus the tsconfig reference (one page per option — read the page for every flag in the Day 1 config above). Watch: 081 — Compiling TypeScript (sandbox file, downlevel emit, --watch) then 085 — Project Workflow with tsconfig (src, outDir, include — including the stray-root-file trap: TS6059, fixed with a top-level fence. Also preserved: Shaun deferring modules with “Um, then we have this one, the module, using Common JS, we’ll come to that later” — the modules paragraph in this chapter’s JavaScript baseline collects on that promise).

Project structure follows one convention through the whole course:

first-project/
  package.json
  tsconfig.json
  src/
    hello.ts
    hello.test.ts
  solution/
    hello.ts

src/ holds the code you edit and its tests; solution/ holds a solved copy you may consult after attempting the lab yourself. node_modules/ holds installed tools and is never committed — .gitignore excludes it (see the git section below).

Node, npx, and environments

Node.js runs JavaScript (and compiled TypeScript tooling) outside the browser. This course needs Node 18 or newer; check with node -v. If the command is missing, install the current LTS from https://nodejs.org/en/download, reopen the terminal, and verify again.

npm ships with Node and manages the node_modules/ toolbox plus the scripts in package.json:

{
	"scripts": {
		"test": "vitest run src/hello.test.ts",
		"typecheck": "tsc --noEmit"
	}
}

npm test runs the lab suite; npm run typecheck runs the compiler check. npx runs a binary from the local toolbox without installing it globally — npx tsc --noEmit uses the project’s own TypeScript version, so every teammate checks with the same compiler. Never install course tools globally; the per-project install recorded in package.json is what keeps npm install reproducible for the next person.

New here: Node as a runtime, npm scripts, npx. Watch: 152 — What Is Node.js (“Node.js is not a programming language. It is a server-side runtime environment.” — one language, two runtimes, and everything after is what that relocation unlocks), then 154 — TypeScript Setup for Node.js (VS Code, inference, @types) and 155 — TypeScript with Express and Node (tsc init, scripts, rimraf, concurrently — the script pattern above, production-flavored). Hands-on: run node -v (need 18+), then npm run typecheck in the lab kit before writing a line — silence is your first green.

VS Code: setup for success

VS Code is the course editor, and its TypeScript support is built in — the same language engine that powers tsc underlines errors as you type. Four pieces of setup turn it from a text box into a coach.

First, install three extensions: the built-in TypeScript language support (verify it is enabled), ESLint (flags probable mistakes as you type), and Prettier (formats on save so style arguments end forever). Error Lens is strongly recommended: it prints each diagnostic inline, next to the guilty line, so you never have to hover to learn what broke.

Second, switch on format on save, lint on save, and autosave. The loop you want is: type, save (fixes itself, flags the rest), read the inline error, fix, repeat. Every second spent arranging whitespace by hand is a second not spent on types.

Third, use the workspace TypeScript version: open any .ts file, run the command “TypeScript: Select TypeScript Version”, and pick “Use Workspace Version”. This binds the editor to the same compiler npm run typecheck uses, so editor squiggles and terminal output can never disagree.

Fourth, check in the .vscode/ folder. extensions.json recommends the extensions above to every teammate; settings.json records format-on-save, the default formatter, and the workspace TypeScript choice; launch.json stores the debug recipe from the next section. A new clone then behaves identically on every machine. The first-project lab kit ships a working .vscode/ — open it, read each file, and copy the pattern into every project you start.

VS Code debugging: launch configs, breakpoints, sourcemaps

When reading an error is not enough, run the code under the debugger. A launch configuration in .vscode/launch.json tells VS Code how:

{
	"version": "0.2.0",
	"configurations": [
		{
			"type": "node",
			"request": "launch",
			"name": "Debug current test",
			"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
			"args": ["run", "${file}"],
			"console": "integratedTerminal"
		}
	]
}

Set a breakpoint by clicking left of a line number in any .ts file — execution pauses there and you can inspect every binding. Sourcemaps are the quiet machinery that makes this work: the debugger runs compiled JavaScript but shows you the TypeScript you wrote, mapping each running line back to its source. Vitest and tsx-style runners handle this mapping for you; if you ever compile with tsc yourself and want to debug the output, enable "sourceMap": true in tsconfig.json so the .js.map files exist. Debug the test, hover the value, find the wrong shape, fix the type — that loop resolves more confusion than any amount of staring.

New here: launch configs, breakpoints, sourcemaps. Watch 079 — TypeScript for Beginners, the Full Hour again, Part A only — its debugging walkthrough is the visual companion to the config above. Hands-on: set one breakpoint in the lab’s hello.test.ts, run the “Debug current test” config, and hover a binding before reading further.

Git basics: the clean-repo habit

Git records what changed, when, and why. The daily vocabulary is small: clone copies a repository to your machine; add stages files for the next snapshot; commit takes the snapshot with a message describing it; branch starts a named line of work; diff shows unstaged changes before you commit them.

.gitignore lists files Git must never track. Every course project ignores at least node_modules/ (rebuilt by npm install), editor swap data, and compiled output:

node_modules/
dist/
*.local

The clean-repo habit: before every commit, run git status, read what is staged, and confirm nothing generated or private slipped in. A repository where git status is quiet and every file is either source or declared configuration is a repository a stranger — or you, in six months — can clone and run with two commands. The first-project kit ships the .gitignore above; keep it.

DIY simulator drills

No tooling needed — you are the simulator. Cover the answers, run each drill in your head or on paper, then verify with the tool itself. Wrong predictions are the lesson; log each miss in one sentence.

  1. Be the compiler. Under strict: true, which lines error, and with what complaint?

    let nickname: string;
    nickname = "Zola";
    const crew: string = 5;
    const label = `crew ${crew.toUpperCase()}`;
    

    Verify: paste into any .ts file, run npx tsc --noEmit, read only the first error. (Drills the “read the first error” habit — later errors are fallout.)

  2. Be the runtime. No types, just JavaScript — what prints?

    console.log("badge " + 3 - 1);
    console.log(0 ?? "Fire crew");
    console.log(0 || "Fire crew");
    

    Verify with node -e. This is the silent-failure family the “Why TypeScript” section promised; feel each one before the compiler takes them away in Chapter 02.

  3. Be the watcher. A project has src/ sources and rootDir: "./src" / outDir: "./public" set, but no include yet; a stray notes.ts sits at the root. You run npx tsc --watch. Does the watcher stay quiet? (Answer: no — the trap from video 085: error TS6059, the stray file is not under rootDir, “and the watcher says so loudly”. The fix is a top-level fence, "include": ["src"], not another compiler option. Reproduce it in the lab kit with an actual stray file, read the TS6059, fence it, delete the stray.)

  4. Be the debugger. greet(" ") throws per its contract, and the stack points at the throw line, not the caller. Without running anything, write down the two clicks that land you on the guilty caller frame (hint: the section above names both). Verify in VS Code with the lab config.

Lab: set up and compile your first project

Download the first-project kit, run its setup script, and watch the suite go red — red first, always. Open src/hello.ts (its header comment carries the full procedure), implement the two functions, and drive the suite green with npm test, then confirm zero type errors with npm run typecheck. Break one function once on purpose, watch exactly one test go red, then restore green. The habit — red, green, deliberate red, green — is the deliverable of Day 1.

Resources

Docs, in the order the chapter introduces them:

Videos — the chapter’s shelf, section-mapped (quoted lines are the instructor’s own words as preserved in each companion entry):

Hands-on: drill set above (paper first, tools second), then the first-project lab kit — red, green, deliberate red, green.