TypeScript Foundations

Lesson 1 of 9

From JavaScript to TypeScript

"TypeScript is JavaScript that scales." — the TypeScript project's original tagline

Welcome to Volume 2. You finished Volume 1 having learned JavaScript end to end — the language, its runtime, its quirks, and its libraries — and the final chapter there made the case for why a language like TypeScript had to exist: JavaScript's freedom is glorious until a codebase grows, a team grows, or a user.emial typo ships to production and nobody finds out until a customer does. This volume is the answer. Over the next nine parts you will go from "I've heard of types" to genuinely fluent — able to model a domain in types, read a gnarly generic signature at a glance, and reach for TypeScript the way you already reach for const. We begin, sensibly, by getting you productive: what TypeScript actually is, how to run it, and the tight feedback loop that will change how you write code.

The question this chapter answers

You already know JavaScript. So what does adding TypeScript really change — in your tools, in your feedback loop, and in the code that ultimately ships to your users?

Core Concepts

TypeScript is a typed superset of JavaScript. Read that phrase precisely, because both halves matter. Superset means every valid JavaScript program is already a valid TypeScript program — there is no rewrite, no new syntax to learn before you can start. Typed means TypeScript adds one thing on top: optional type annotations, small notes that describe the shape of your data, which a compiler checks before your code ever runs. Catch the bug at author time, not at 3am from a pager.

The third fact is the one that surprises people, and it is the key to the whole design: types are erased. TypeScript is not a runtime. When you build, the compiler checks your annotations, then throws them away, emitting ordinary JavaScript — the same JavaScript you'd have written by hand. There is no TypeScript engine in the browser or in Node. The types exist to help you and your tools; at runtime they simply are not there. Zero runtime cost, zero runtime magic.

Setting up the toolchain

TypeScript is a package you install into your project. It ships one binary you'll use constantly — tsc, the TypeScript compiler.

npm i -D typescript      # add the compiler as a dev dependency
npx tsc --init           # generate a tsconfig.json
npx tsc                  # type-check + compile once, per tsconfig
npx tsc --watch          # re-check on every save (great during dev)

tsc --init writes a tsconfig.json — the project's control panel: which files to include, which JavaScript version to emit, how strict to be. A minimal, modern one:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "strict": true,
    "outDir": "dist",
    "rootDir": "src"
  }
}

The single most important line there is "strict": true. It switches on the full family of checks — no implicit any, strict null handling, and more — and it is the setting this entire volume assumes. Turn it on from day one; retrofitting strictness onto a large loose codebase is a genuinely miserable job, and you never want to teach yourself the sloppy version first.

You don't always need a separate build step to run TypeScript, though. Tools like tsx and ts-node compile-and-run in one motion, which is perfect for scripts and quick experiments:

npx tsx src/main.ts      # run a .ts file directly, no visible build
Everyday analogy

Think of TypeScript as spell-check and grammar-check for code. The prose you ship is still plain text — the reader never sees the squiggly red underlines. But while you write, those underlines catch the teh and the "their/there" before anyone else does. TypeScript is that layer for your program: it reads over your shoulder, flags what won't make sense, and then gets out of the way when the document goes out the door.

The dev loop: two places the same check runs

The check that makes TypeScript feel magical runs in two places, and both matter. In your editor, a background language server re-type-checks as you type and draws red squiggles under mistakes — you learn a call was wrong while your fingers are still on it. Then in CI, tsc --noEmit runs the exact same check on every push, so a mistake that slips past one developer's editor can't merge. Same compiler, same rules, two safety nets: one for speed of feedback, one for the guarantee. The error is caught at author time, not at run time — that shift is the entire value proposition.

All JavaScript is already TypeScript

Because TS is a superset, adoption can be a single command: rename app.js to app.ts and you have valid TypeScript. Nothing else is required. TypeScript will immediately start inferring types for you — it sees const count = 0 and knows count is a number without you saying so — and it will only complain where it can prove something is off. You then add explicit annotations where they earn their keep: function boundaries, tricky data shapes, anywhere a reader (including future-you) benefits from the intent being written down.

Your first types

An annotation is a colon and a type. Here it is on a variable and, in the next block, on a function. Watch a wrong assignment turn into a compile error you can see in the editor panel:

TypeScript — Try it Yourself
Loading editor…
Type check + Console

That red mark appears at compile time. Now a function, with the required deliberate mistake — passing a string where a number is expected — followed by the corrected call that actually logs a result:

TypeScript — Try it Yourself
Loading editor…
Type check + Console

Notice something important about that double("21") line: the type panel shows the ✗, but the emitted JavaScript still runs — TypeScript reports the problem, it doesn't stop you at gunpoint. In a real project, tsc in CI is what turns that report into a hard gate. The type system is advisory to the runtime and mandatory to your pipeline.

History changed here

TypeScript was created at Microsoft and released in 2012, led by Anders Hejlsberg — the same engineer behind Turbo Pascal, Delphi, and C#. The radical bet was gradual, structural, erasable typing: don't replace JavaScript, embrace it; don't force types everywhere, allow them; and above all, compile down to clean JS so the language of the web stays the language that runs. A decade later that bet won so thoroughly that TypeScript routinely tops "most-loved language" surveys and anchors React, Angular, Node tooling, and VS Code itself — which is, fittingly, written in TypeScript.

Type erasure, made concrete

The best way to believe "erased at compile time" is to see the output. Here is a small TypeScript file:

function greet(name: string): string {
  return `Hi, ${name}`;
}

const user: string = "Ada";
console.log(greet(user));

And here is the JavaScript tsc actually emits from it — the exact same logic with every annotation gone:

function greet(name) {
  return `Hi, ${name}`;
}

const user = "Ada";
console.log(greet(user));

The : string annotations, the return type — all vanished. What ships is lean JavaScript. This is why TypeScript adds no runtime overhead and cannot enforce types at runtime: by the time the code executes, there are no types left to check. Remember this the first time you're tempted to trust a type to validate data coming from a network — that's a job for runtime validation, because the type is long gone.

Real-world example — chika.ng Academy

The Academy is a Next.js app backed by Drizzle and MySQL, written in TypeScript top to bottom. When a route reads a certificate record, the row's shape flows — typed — from the database schema, through the query, into the React component that renders it. Rename a column and forget to update the page, and tsc lights up the exact lines that no longer make sense before the build finishes, long before a student ever hits a broken verify page. None of those types exist in the JavaScript shipped to the browser; they did their whole job at author time and CI, then erased themselves.

The engineer's notebook

How this actually shows up in a working TypeScript project:

• Start strict. New project? "strict": true in tsconfig.json on line one. It is far cheaper than adding it later.
• Annotate the boundaries, let inference do the middle. Types on function parameters and public returns; let tsc infer local variables — over-annotating just adds noise.
• Make CI run tsc --noEmit. Editor squiggles help the author; the CI check protects everyone else.
• Use tsx for scripts, a real build for shipping. Fast iteration locally, deterministic output in the pipeline.

Common Mistakes

  • Expecting types at runtime. Types are erased — you cannot if (x instanceof MyType) on an interface, and a string annotation won't stop bad JSON from arriving. Validate untrusted input with real runtime code (or a library like Zod).
  • Leaving strict off "for now." Loose mode lets any creep everywhere and hides exactly the bugs you adopted TypeScript to catch. Retrofitting strictness onto a big loose codebase is the expensive path.
  • Annotating everything. Writing const n: number = 0 is redundant — inference already knows. Reserve explicit types for function signatures and genuinely ambiguous spots; let the compiler carry the rest.
  • Confusing tsc with a bundler or a runtime. tsc type-checks and emits JS; it does not bundle, minify, or run your server. Those are separate tools that sit alongside it.
  • Thinking a red squiggle blocks the run. By default the emitted JS still executes. The gate is tsc failing in CI — wire that up, or errors merge anyway.
Thought experiment

You rename a widely-used field from user.name to user.fullName across a 200-file codebase. In plain JavaScript, how do you know you caught every reference? You grep, you hope, and you find the last one in a Sentry alert next Tuesday. In TypeScript, the moment you change the type, tsc reports every stale user.name as an error — a complete, exhaustive to-do list of the change, generated for free. That difference — "hope you found them all" versus "here is the exact list" — is what people mean when they say types make large codebases tractable.

Interview Corner

Interview Corner — the same idea at three levels. Answer yours; recognise the one above it.
Junior
"What is TypeScript, and how does it relate to JavaScript?"
Model answer — "It's a typed superset of JavaScript — all valid JS is valid TS. It adds optional type annotations that a compiler checks before the code runs, then erases them, emitting plain JavaScript. So you get compile-time safety with zero runtime cost."
Follow-up they'll throw: "So do the types exist when the code runs?" → No. They're erased at compile time; the emitted JS has no types, which is why you still validate external input at runtime.
Intermediate
"Walk me through the feedback loop — where and when does TypeScript actually catch a bug?"
Model answer — "In two places running the same compiler. The editor's language server type-checks as I type and shows squiggles instantly, so I fix issues at author time. Then CI runs tsc --noEmit on every push as a hard gate, so anything my editor missed can't merge. The point is the error surfaces before runtime — before a user ever hits it."
Follow-up: "Does a type error stop the code from running locally?" → Not by default — tsc still emits JS. The enforcement is the CI check failing, so you must wire it up.
Senior
"How would you introduce TypeScript into a large, established JavaScript codebase?"
Model answer — "Incrementally, because it's a superset — I can rename files to .ts or enable allowJs and adopt module by module. I'd start with strict off only to get it compiling, then ratchet strictness up flag by flag, fixing the fallout in bounded PRs, and add tsc --noEmit to CI early so no new loose code lands. I'd type the boundaries first — API responses, database rows, shared utilities — because that's where a wrong shape does the most damage, and let inference cover the interior."
The senior signal is treating adoption as a migration with a ratchet, not a big-bang rewrite — and knowing that types are erased, so runtime validation at trust boundaries is still non-negotiable.
Company lens — how the same topic gets asked
micro1Live and conversational — the AI interviewer wants you reasoning out loud. Define "typed superset," then say the word "erased" unprompted; it signals you understand the model, not just the syntax.
GoogleWants precision on the compile-time/run-time boundary. Expect "what happens if bad data arrives at runtime?" — answer with runtime validation, since types are gone by then.
MicrosoftThey created TypeScript — expect depth. Be ready to discuss tsconfig strictness, structural typing, and the design goal of erasable, gradual types.
Amazon / AWSOperational lens: how types reduce production incidents and how you'd roll adoption across many services without freezing delivery. Tie it to CI gating and safe, incremental migration.
Hands-on exercise
  1. In the editor above, change double(21) to double("21") and read the exact diagnostic. Now change the parameter type from number to string and watch the error move to a different line — you're feeling the type-checker react in real time.
  2. On your own machine: npm i -D typescript, npx tsc --init, then create a src/hello.ts with one typed function. Run npx tsx src/hello.ts to execute it, then npx tsc and open the emitted .js — confirm with your own eyes that the annotations are gone.
  3. Say it out loud, as in a live screen: "TypeScript is a typed superset of JavaScript; the compiler checks types before runtime and then erases them." If you can explain why erasure means no runtime cost, you've got the core.

Chapter Summary

  • TypeScript is a typed superset of JavaScript: all valid JS is valid TS, plus optional type annotations.
  • The compiler tsc checks types before runtime, then erases them, emitting plain JavaScript with zero runtime cost.
  • Set up with npm i -D typescript, tsc --init (a tsconfig.json — keep "strict": true), then tsc, tsc --watch, or run directly with tsx/ts-node.
  • The dev loop runs the same check twice: editor squiggles at author time, tsc in CI as the merge gate.
  • Adoption is incremental — rename .js to .ts; inference does a lot, and you annotate where it helps (boundaries first).
  • Because types are erased, they can't validate runtime data — trust boundaries still need real runtime checks.
Narrative bridge

You can now install TypeScript, run it, and read your first red squiggle with understanding. But we've been hand-waving one word: type. What kinds are there — and what exactly does number, string, or boolean promise the compiler? That's next, in "The Basic Types", where we meet the primitives, arrays, and the type system's building blocks — the vocabulary every later chapter is built from.

Editorial reflection — the temptation is to treat TypeScript as JavaScript with extra ceremony — annotations to appease a compiler. That framing gets it backwards. The types aren't the tax; they're the design tool. Writing the shape of your data down forces you to decide what your data is before you build on it, and a compiler that holds you to that decision is not a nag but a collaborator with perfect memory. You already know JavaScript. This volume is about learning to think in shapes — and the rest of the language follows from there.

Q&A · 0

Enrol to ask questions and join the discussion.

No questions yet — be the first to ask.