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
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.
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.