Lesson 1 of 5

Life Without Compile-Time Types

You're used to a safety net that runs before your code ever executes: a compiler that reads your annotations and refuses to build if a string sneaks into a number's place. Drop back into a plain JavaScript codebase — a legacy project, a coworker's script, a library without types — and that net is just gone. Nothing stops incompatible values from touching. Nothing runs before runtime. The bugs TypeScript used to catch for you don't disappear — they just move to production, or to whoever happens to run the code next.

💡 The Bottom Line

Plain JavaScript has no compile step and no type checker — every value is implicitly whatever TypeScript would call any, everywhere, all the time, and nothing warns you when that's a problem. Editor autocomplete gets dumber too, since it was reading your interface/type declarations to know what properties exist; without them, it can only guess from whatever object literal happens to be nearby. The only feedback loop left is running the code and seeing what breaks.

The same function, with and without the net

TypeScript
function applyDiscount(
  price: number,
  percent: number
): number {
  return price - (price * percent) / 100;
}

applyDiscount(100, "10");
// compile error: Argument of type
// 'string' is not assignable to
// parameter of type 'number'.
// Caught before the code ever runs.
Plain JavaScript
function applyDiscount(price, percent) {
  return price - (price * percent) / 100;
}

applyDiscount(100, "10");
// no error anywhere.
// "10" coerces in arithmetic,
// returns 90 — looks right by luck.

applyDiscount(100, "ten");
// still no error.
// returns NaN. Nothing tells you
// until it shows up on a receipt.

📋 Key differences

  • TypeScript's applyDiscount(100, "10") fails to compile — you find out at write-time, in your editor, before deploying anything.
  • Plain JS's identical call compiles (there's nothing to compile) and often *works by accident*, because "10" coerces fine in a multiplication — the danger is that it looks correct right up until someone passes a value that doesn't coerce cleanly.
  • There's no distinction between "this parameter should always be a number" and "I have no idea what this parameter is" — both look exactly the same: unannotated.

What editor support quietly loses

A chunk of what feels like "TypeScript's type checking" is actually your editor reading those types to power autocomplete — suggesting the right property names on an object, flagging a typo in a method call, showing you a function's expected parameters as you type the call. That tooling is driven by declared types, not psychic guessing. In plain JS, an editor can still infer something from an object literal that's visibly assigned nearby, but the moment a value arrives from a function return, a network response, or another file, that inference runs out, and autocomplete quietly stops being trustworthy.

⚠️ Gotcha

The most dangerous class of bug in untyped JS isn't the one that throws — it's the one that doesn't. undefined.someMethod() throws immediately and loudly. price * "ten" silently produces NaN and keeps executing, often for several more function calls, before that NaN finally surfaces somewhere visible (a broken UI number, a corrupted total). TypeScript used to catch this class of error at the source; without it, you're debugging backward from the symptom to a root cause that could be several calls upstream.

🔧 Try It Yourself

Take any small TypeScript function with parameter and return type annotations, strip every annotation out by hand, and run it with a deliberately wrong argument type (a string where a number was expected, an object missing a field). Watch what actually happens at runtime — does it throw immediately, silently produce NaN/undefined, or appear to work until a later line depends on the bad value?

🙋 There Are No Dumb Questions

Q: If I just write careful, disciplined JavaScript, do I really lose anything TypeScript gave me?
A: You lose the guarantee, not the possibility. Careful JS developers can absolutely write code that behaves as if it were type-safe — but TypeScript's actual value was never "you could have written this correctly," it was "the compiler refuses to let this specific mistake through, for every file, every time, including on a tired Friday afternoon or in code a teammate wrote without your context." In plain JS, correctness depends entirely on discipline holding up every single time; nothing enforces it structurally.

✅ Quick Check

In plain JavaScript, applyDiscount(100, "ten") is called where the function expects two numbers. What actually happens?


Next up: you can't get the compiler back without a build step — but a JSDoc comment and a single line at the top of a file can claw back a surprising amount of the editor support you just lost.

← Back to
Next →