TypeScript is now a default choice for many JavaScript projects. A grounded look at what static types prevent, where they stop helping, and why you still need runtime checks.
Many teams now start new JavaScript projects in TypeScript. The reasons are practical rather than fashionable: fewer silly bugs, better editor help and safer changes in big codebases.
But types are not a magic shield, and misunderstanding what they cover leads to false confidence.
What types catch early
TypeScript checks your code before it runs. Mistakes that would normally show up as a crash in the browser appear as a red underline in your editor.
type User = { id: number; name: string };
function greet(user: User) {
return "Hello " + user.name.toUpperCase();
}
greet({ id: 1, name: "Asha" }); // fine
greet({ id: 2 }); // error: property "name" is missing
greet({ id: 3, nme: "Ravi" }); // error: typo caught before running- Typos in property names.
- Passing a string where a number is expected.
- Forgetting to handle
nullorundefined. - Calling a function with the wrong arguments after a refactor.
The quiet benefit: safer refactoring
Rename a field, change a function’s arguments, or split a type in two, and TypeScript lists every place that now needs fixing. In a large project that turns a risky change into a checklist.
Types also act as living documentation. Hovering over a function tells you what it needs and what it returns, without opening another file.
The limit: types vanish at runtime
Here is the part that catches people. TypeScript types exist only while you write and build. They are erased from the final JavaScript. So TypeScript cannot check the data that arrives from outside your program, such as an API response, a form, or a file.
// The type says this is a User, but the server could send anything.
const data = (await res.json()) as User; // a promise, not a proof
// Check the real shape at the boundary instead.
function isUser(x: unknown): x is User {
return (
typeof x === "object" && x !== null &&
typeof (x as any).id === "number" &&
typeof (x as any).name === "string"
);
}
const raw = await res.json();
if (!isUser(raw)) throw new Error("Unexpected response shape");
// from here on, raw is safely a UserMany teams use a validation library to define the shape once and get both the runtime check and the TypeScript type from it.
Habits that keep types useful
- Turn on strict mode. It catches far more than the default settings.
- **Avoid
any.** Eachanyswitches off checking for that value. Preferunknownand narrow it. - **Be careful with
as.** A type assertion tells the compiler to trust you. It does not check anything. - Do not over-type. Let TypeScript infer simple types. Write them out where they clarify intent, such as function inputs and outputs.