Skip to content

Why TypeScript?

Here is plain JavaScript that looks fine and fails at runtime:

function greet(user) {
return "Hello, " + user.name.toUpperCase();
}
greet({ nam: "Ada" }); // typo: `nam` not `name`
// 💥 TypeError: Cannot read properties of undefined (reading 'toUpperCase')

The typo isn’t found until that line executes — maybe in production, maybe for one unlucky user. TypeScript moves that discovery to compile time:

function greet(user: { name: string }) {
return "Hello, " + user.name.toUpperCase();
}
greet({ nam: "Ada" });
// ❌ Error: Object literal may only specify known properties,
// and 'nam' does not exist in type '{ name: string }'.

You find out the instant you write it, in the editor, before anything runs. That shift — from “fails later, somewhere” to “fails now, right here” — is the whole value proposition.

Compile time vs runtime: the crucial split

Section titled “Compile time vs runtime: the crucial split”

TypeScript has two completely separate phases, and confusing them is the source of most beginner mistakes.

flowchart LR
  ts[".ts source
(types + logic)"] -->|tsc: type-check| check["Type checking
(errors reported here)"]
  ts -->|tsc: emit| js[".js output
(types ERASED)"]
  js --> run["Runtime
(no types exist)"]
Types are checked, then erased
  • Compile time — the type checker reads your annotations and inferred types and reports errors. This is where all the safety happens.
  • Runtime — the emitted JavaScript runs. The types are gone. They were erased during compilation.

This is why you cannot do this:

function isString(x: unknown) {
return x is string; // ❌ not a thing — types don't exist at runtime
}

At runtime there is no string type to check against — only the JavaScript value. To narrow types at runtime you use real JavaScript operations (typeof x === "string") that the compiler understands. More on that in the narrowing lesson.

Compile this:

interface User { name: string; age: number; }
const u: User = { name: "Ada", age: 36 };
function older(u: User): number { return u.age + 1; }

The emitted JavaScript is:

const u = { name: "Ada", age: 36 };
function older(u) { return u.age + 1; }

The interface vanished entirely; the annotations are stripped. Types add zero runtime cost and zero runtime behavior — they are a conversation between you and the compiler that ends before the program runs.

TypeScript is not free, and pretending otherwise does you a disservice:

  • A build step. You need tsc or a bundler that strips types. Plain .ts doesn’t run in Node or the browser directly (though runtimes are increasingly adding type-stripping).
  • A learning curve. The type system is a language of its own — this whole course exists because of that.
  • Type/runtime gap. The compiler can be wrong about runtime reality — an any, a bad cast, or unvalidated external data can lie to it. Types are a model, not a guarantee about the outside world (the Practical Mastery module is largely about this).

The trade is almost always worth it for anything beyond a throwaway script: you pay a fixed tax and get a large, compounding return in caught bugs, safe refactoring, and editor intelligence.

What happens to TypeScript types when the code is compiled to JavaScript?
What is the core value proposition of TypeScript?
Why can't you check a TypeScript type directly at runtime?
Which is a genuine cost of adopting TypeScript?