TypeScript for JavaScript Developers: What Changes and What Does Not
A type system that exists only until your code runs — and why that is the point.

A type system that exists only until your code runs — and why that is the point.

The most important fact about TypeScript is one people often meet too late: none of it exists at runtime. Types are checked while you write and compile, then stripped away completely. What ships to the browser is plain JavaScript.
That single fact explains most of what confuses newcomers. Why you cannot check a type in an if statement the way you would expect. Why data arriving from an API is not really validated just because you declared its shape. Why TypeScript catches so much and yet lets certain bugs straight through.
Understood properly, it is one of the highest-value additions to a JavaScript codebase. Understood as runtime safety, it becomes a false sense of security.

Three things, in rough order of value.
Misspelled property names, forgotten arguments, functions called with the wrong shape of object, code paths where a value could be undefined — these stop being runtime surprises and become red underlines while you write.

Autocomplete becomes reliable rather than aspirational. Rename across a project becomes safe. Go-to-definition works. For many people this is the change that makes them never go back, more than the error checking.
A function signature stating exactly what it takes and returns is documentation the compiler enforces. Unlike a comment, it cannot quietly drift out of date, because the code stops compiling when it does.
This is where teams get hurt. Declaring that a fetch returns a User tells the compiler what you expect. It does not check anything at runtime. If the API returns a different shape, your program will happily proceed with wrong data and fail somewhere far from the cause.
Anywhere data crosses into your program from outside — network responses, form input, local storage, environment variables, query parameters — you need genuine runtime validation. A schema validation library does this and can derive the TypeScript type from the same schema, so the two cannot disagree.
A type assertion (as User) is not a conversion or a check. It is you telling the compiler to stop asking questions. Every assertion is a place where a runtime surprise can enter unannounced, which is why they should be rare and commented.
TypeScript works out most types on its own. Annotating every local variable is noise. Annotate the boundaries — function parameters, return types of exported functions, the shape of external data — and let inference handle the interior.
A union says a value is one of several things: a string or null, a status that is pending, active or cancelled. Narrowing is how you convince the compiler which one you have, using ordinary checks like typeof, in or a simple if.
This pairing is TypeScript at its best. It makes impossible states genuinely impossible to represent, and it forces you to handle the null case rather than discovering it in production.
Generics let a function work with any type while preserving the relationship between input and output — an identity function returns exactly what it was given, not an unknown. Learn them when you find yourself writing the same function three times for three types, and not before.
| Feature | Use it for | Common mistake |
|---|---|---|
| Type inference | everything the compiler can work out | annotating every local variable |
| Interfaces and types | the shape of objects and functions | arguing about which to use |
| Union types | values that are one of several options | reaching for 'any' instead |
| Generics | reusable code that preserves types | using them where a union would do |
| 'unknown' | data you have not validated yet | using 'any' and losing all checking |
Turn on strict mode from the beginning of a new project. It is far easier to start strict than to tighten later, and the biggest single win — strictNullChecks, which forces you to handle null and undefined — is inside it.
You do not need a rewrite, and you should not attempt one. TypeScript is designed for gradual adoption.

A team of five decided to adopt TypeScript on a three-year-old React application. They enabled every strict flag on day one and were greeted by roughly 4,000 errors.
Two weeks of work later, morale was poor, no features had shipped, and there was a serious proposal to abandon the whole effort.
They restarted differently: loose settings, allow JavaScript, and a rule that any file you edit for other reasons gets converted. Six months later the codebase was around 80% TypeScript, strict mode was on for the converted portion, and nobody had spent a single sprint on migration.
any switches off type checking for a value entirely. It is occasionally the right answer, and it is very often a way of postponing a problem — and because any spreads through everything it touches, one of them can quietly disable checking across a whole call chain.
When you genuinely do not know a type, use unknown instead. It forces you to check before using the value, which is exactly the behaviour you want at the boundary of your system.
TypeScript does not make your program correct. It makes a large category of your mistakes impossible to write down without noticing.
— A realistic summary

TypeScript checks types while you write and erases them before you run. It catches typos, wrong shapes and unhandled nulls, and it makes editor tooling genuinely useful. It does not validate external data, so schema validation at the boundaries is still required. Adopt gradually, prefer inference, keep any rare, and turn on strict mode early.
The value is not really about types. It is about how quickly you can change code you did not write, on a Tuesday, without breaking something you did not know existed.

That confidence compounds with team size and codebase age. If your project is small and short-lived, plain JavaScript is fine. If it will outlive your memory of writing it, the types pay for themselves. For the wider picture of readable, changeable code, see writing code humans can read.
Tap a star to share what you thought.
No ratings yet
No. Types are removed during compilation, so the JavaScript that runs is equivalent to what you would have written by hand. The cost is at build time and in your editor, not at runtime.
No. Declaring a response type tells the compiler what you expect; nothing checks it at runtime. Use a schema validation library at your boundaries, and derive the TypeScript type from the same schema so they cannot drift apart.
Both mean the type is not known, but 'any' disables checking entirely and spreads through everything it touches, while 'unknown' forces you to narrow the value before using it. Prefer 'unknown' at the edges of your system.
Sign in to join the conversation.
Loading responses…
Have a story, idea, or something valuable to share? Join The Blog Story for free, publish your content, reach more readers, and earn a share of advertising revenue from eligible content.
Create quality content. Grow your audience. Grow your earning potential.
Either works for object shapes. Interfaces can be extended and merged, which suits public API surfaces; type aliases handle unions and other constructs that interfaces cannot. Pick one convention per codebase and stop debating it.
Yes, and gradually is the only approach that works well. Start with loose settings and allowJs, convert small leaf modules first, then external data types, and enable strictness one flag at a time.
A group of stricter checks, the most valuable being strictNullChecks, which forces you to handle null and undefined explicitly. Starting a new project with strict enabled is far easier than retrofitting it later.
Convincing the compiler which member of a union type you currently have, using ordinary runtime checks such as typeof, instanceof, the 'in' operator or a simple comparison. After the check, the compiler knows the specific type inside that block.
When you notice yourself writing the same function repeatedly for different types. Generics preserve the relationship between input and output types. Reaching for them earlier than that usually produces harder code for no benefit.