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.
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 category | JavaScript result | TypeScript result |
|---|---|---|
| Misspelled properties | undefined, possible crash later | Compiler error with fix suggestion |
| Wrong argument types | Unexpected behavior, silent bug | Compiler error at the call site |
| Missing null checks | TypeError at runtime | Compiler requires a check |
| Wrong array or object shape | Runtime errors in loops | Compiler validates the shape |
| API response mismatches | Garbled data, hard-to-trace bugs | Compiler 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.
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:
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.
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 3All 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.
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:
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:
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.
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:
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:
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.
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:
let age: number = 25; // age must always be a number
let name: string = "Alex"; // name must always be a stringAfter 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:
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
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.
Frequently Asked Questions
Does TypeScript catch all bugs?
Can I still have runtime errors in TypeScript?
Does TypeScript slow down development?
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.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.