How TypeScript Helps Catch Bugs Early

TypeScript catches bugs at compile time that JavaScript only finds at runtime. See real examples of the most common errors TypeScript prevents.

6 min read

TypeScript catches bugs by checking your code before it runs. Instead of discovering a misspelled property or a wrong argument when the code crashes in production, you see the problem in your editor while you are still typing.

This shift from runtime errors to compile-time errors is the single biggest reason teams adopt TypeScript. It turns a category of bugs from "we will find out later" into "you cannot write this."

The Five Categories TypeScript Eliminates

TypeScript prevents five common classes of bugs that JavaScript developers deal with every day:

Bug categoryJavaScript resultTypeScript result
Misspelled propertiesundefined, possible crash laterCompiler error with fix suggestion
Wrong argument typesUnexpected behavior, silent bugCompiler error at the call site
Missing null checksTypeError at runtimeCompiler requires a check
Wrong array or object shapeRuntime errors in loopsCompiler validates the shape
API response mismatchesGarbled data, hard-to-trace bugsCompiler validates against expected type

Let's look at each category with real code.

Bug 1: Misspelled Properties

This is the most common typo in JavaScript. You mistype a property name and the bug might not surface until a user clicks the wrong button.

typescripttypescript
type Product = {
  title: string;
  price: number;
  inStock: boolean;
};
 
function displayProduct(product: Product) {
  console.log(product.tittle);    // Compiler error
  console.log(product.prics);     // Compiler error
}

TypeScript reports both typos immediately, before you run any code. It points to the exact line and even suggests the correct property name so you can fix it right away:

texttext
Property 'tittle' does not exist on type 'Product'. Did you mean 'title'?
Property 'prics' does not exist on type 'Product'. Did you mean 'price'?

The compiler not only catches the misspelling but suggests the correct property. In JavaScript, product.tittle silently returns undefined, and you might ship code that shows a blank title to users.

Bug 2: Wrong Function Arguments

Functions are the backbone of any codebase. Passing the wrong type of argument is easy to do and hard to debug when it happens silently.

typescripttypescript
function calculateTotal(price: number, quantity: number): number {
  return price * quantity;
}
 
calculateTotal("19.99", 3);   // Compiler error: string is not number
calculateTotal(19.99);         // Compiler error: expected 2 arguments, got 1
calculateTotal(19.99, 3, 5);  // Compiler error: expected 2 arguments, got 3

All three wrong calls are blocked. TypeScript checks the count and the type of every argument.

In plain JavaScript, the first call would produce NaN, the second would also produce NaN, and the third would silently ignore the extra argument. All three would run without any warning.

Bug 3: Missing Null Checks

The infamous "cannot read properties of null" error is one of the most frequent runtime crashes in JavaScript applications. TypeScript with strict mode makes it impossible to forget.

typescripttypescript
type User = {
  name: string;
  email: string;
};
 
function getUser(id: number): User | null {
  // Pretend this looks up a user. Returns null if not found.
  return id === 1 ? { name: "Alex", email: "alex@example.com" } : null;
}

The getUser function can return either a User or null, so TypeScript will not let you treat the result as a guaranteed User. Trying to read a property directly proves the point:

typescripttypescript
const user = getUser(2);
console.log(user.name);  // Compiler error: 'user' is possibly 'null'

The compiler warns you that user might be null before you ever run the code, and you must handle that case explicitly:

typescripttypescript
const user = getUser(2);
 
if (user === null) {
  console.log("User not found");
} else {
  console.log(user.name);  // Safe: TypeScript knows user is not null here
}

After the null check, TypeScript narrows the type. Inside the else block, user is known to be User, not User | null. The compiler tracks this narrowing through every if, switch, and early return. See type narrowing in TypeScript for how this works.

Bug 4: Wrong Array or Object Shape

JavaScript objects are shapeless at runtime. If you expect an array of objects with a certain shape but get something different, bugs spread through loops and callbacks before anything visibly breaks.

typescripttypescript
type Order = {
  id: number;
  total: number;
  items: string[];
};
 
function printOrderSummaries(orders: Order[]) {
  for (const order of orders) {
    console.log(`Order #${order.id}: has ${order.items.length} items`);
  }
}

Every object passed to printOrderSummaries must match the Order shape. An array like [{ id: 1, total: 29.99, items: ["book", "pen"] }] compiles fine because every field matches. An array with the wrong shape does not:

typescripttypescript
printOrderSummaries([
  { id: 2, total: "14.99", items: [] },
  { id: 3, total: 9.99 },
]);

TypeScript checks every object in the array against that shape and reports two separate problems here, one for each broken order in the list:

texttext
Type 'string' is not assignable to type 'number'.
Property 'items' is missing in type '{ id: number; total: number; }'.

A single mismatched property in either object stops compilation before the code ever runs.

Bug 5: API Response Mismatches

Code that fetches data from an API is particularly fragile. The API might change its response shape, and JavaScript will happily use the new shape incorrectly until something breaks.

typescripttypescript
type ApiUser = {
  id: number;
  name: string;
  email: string;
};
 
async function fetchUser(id: number): Promise<ApiUser> {
  const response = await fetch(`https://api.example.com/users/${id}`);
  const data: ApiUser = await response.json();
  return data;
}

The ApiUser type annotation on the response does not validate the data at runtime. But it tells TypeScript what shape to expect everywhere fetchUser is used. If a teammate changes the type to add a required field, every place that uses the response gets a compile error until it handles the new field.

For actual runtime validation of API responses, see runtime validation in TypeScript.

How the Compiler Decides What to Catch

TypeScript's error detection is not magic. It works because you describe what your data looks like:

typescripttypescript
let age: number = 25;       // age must always be a number
let name: string = "Alex";  // name must always be a string

After these declarations, TypeScript tracks every assignment, function call, and property access involving age and name. If you try to assign a string to age or call .toFixed() on name, the compiler flags it.

The more you describe your types, the more the compiler can check. The baseline is strict: true in your tsconfig, which enables the full set of type checks. See TypeScript compiler explained for the full list.

What TypeScript Does Not Catch

TypeScript catches type bugs, not logic bugs. This function still has a mistake:

typescripttypescript
function applyDiscount(price: number, discountPercent: number): number {
  return price - discountPercent;  // Wrong logic: should be price * (1 - discountPercent/100)
}

The types are correct. Both arguments are numbers, and the return value is a number, so TypeScript is happy.

But the math is wrong. Type safety does not replace testing and code review.

Runtime errors from network failures, file system errors, and external services still happen. TypeScript only operates at compile time. You still need error handling for runtime conditions.

Rune AI

Rune AI

Key Insights

  • TypeScript catches type errors at compile time, before users ever see them.
  • Property typos, wrong function arguments, and null reference errors are caught in the editor.
  • TypeScript forces you to handle missing values, preventing NullPointer-like crashes.
  • The type system also validates data shapes from APIs and external sources.
  • You still need runtime error handling for network and file system errors.
RunePowered by Rune AI

Frequently Asked Questions

Does TypeScript catch all bugs?

No. TypeScript catches type-related bugs like wrong argument types, misspelled properties, and null reference errors. It does not catch logic errors, incorrect algorithms, or business rule mistakes.

Can I still have runtime errors in TypeScript?

Yes. Network failures, file system errors, and unexpected external data can still cause runtime errors. TypeScript reduces the types of errors you get, but you still need error handling for runtime conditions.

Does TypeScript slow down development?

Initially, you spend a few extra seconds writing type annotations. But you save far more time by not hunting down type bugs in the debugger. Most developers report faster development overall with TypeScript.

Conclusion

TypeScript catches the most common and most expensive category of bugs: type errors. Misspelled properties, wrong function arguments, missing null checks, and bad API responses all become compile-time errors instead of runtime crashes. The few minutes you spend writing types pay back hours of debugging time.