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.

example.ts
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 null or undefined.
  • 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.

Types protect the inside. The boundary needs a runtime check.
validate.ts
// 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 User

Many 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.** Each any switches off checking for that value. Prefer unknown and 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.