DEV Community

Cover image for TypeScript Mistakes That Cost You Hours
Pixel Mosaic
Pixel Mosaic

Posted on

TypeScript Mistakes That Cost You Hours

TypeScript is one of the best investments you can make in a JavaScript codebase. It catches bugs before production, improves IDE support, and makes large applications easier to maintain.

But here's the catch: TypeScript only helps when you use it correctly.

Over the years, I've seen developers (myself included) make the same mistakes repeatedly. These mistakes don't always produce compiler errors, they often create false confidence, hidden bugs, and hours of debugging.

Let's look at some of the most common ones.

1. Using any Everywhere

The fastest way to silence TypeScript is also the fastest way to lose its benefits.

❌ Bad

function processUser(user: any) {
  return user.name.toUpperCase();
}
Enter fullscreen mode Exit fullscreen mode

This compiles even if user.name doesn't exist.

✅ Better

interface User {
  name: string;
}

function processUser(user: User) {
  return user.name.toUpperCase();
}
Enter fullscreen mode Exit fullscreen mode

Or, if the shape is unknown:

function processUser(user: unknown) {
  if (
    typeof user === "object" &&
    user !== null &&
    "name" in user
  ) {
    return String(user.name).toUpperCase();
  }
}
Enter fullscreen mode Exit fullscreen mode

Rule: Use unknown instead of any whenever possible.

2. Ignoring strict Mode

Many projects still start with:

{
  "strict": false
}
Enter fullscreen mode Exit fullscreen mode

This disables many of TypeScript's most useful safety checks.

Enable:

{
  "compilerOptions": {
    "strict": true
  }
}
Enter fullscreen mode Exit fullscreen mode

You'll immediately catch issues like:

  • undefined values
  • missing properties
  • incorrect function calls
  • unsafe assignments

Yes, migration takes time, but the payoff is huge.

3. Overusing Type Assertions

Developers often force TypeScript to agree with them.

const user = response as User;
Enter fullscreen mode Exit fullscreen mode

This tells TypeScript:

"Trust me."

Even when it shouldn't.

A safer approach is validating data.

if ("name" in response) {
  console.log(response.name);
}
Enter fullscreen mode Exit fullscreen mode

Or use runtime validation libraries like:

  • Zod
  • Valibot
  • io-ts

Type assertions should be the exception, not the default.

4. Confusing interface and type

Many developers think they're interchangeable.

Not exactly.

Use interface for object contracts.

interface User {
  id: number;
  name: string;
}
Enter fullscreen mode Exit fullscreen mode

Use type for unions, intersections, tuples, and utility types.

type Status =
  | "loading"
  | "success"
  | "error";
Enter fullscreen mode Exit fullscreen mode

A simple rule:

  • Object shape → interface
  • Everything else → type

5. Forgetting to Narrow Union Types

Consider this example:

type Result =
  | { success: true; data: string }
  | { success: false; error: string };

function handle(result: Result) {
  console.log(result.data);
}
Enter fullscreen mode Exit fullscreen mode

TypeScript complains because data isn't always available.

Correct:

function handle(result: Result) {
  if (result.success) {
    console.log(result.data);
  } else {
    console.log(result.error);
  }
}
Enter fullscreen mode Exit fullscreen mode

Union types are powerful, but only when properly narrowed.

6. Making Everything Optional

This interface looks flexible.

interface User {
  id?: number;
  name?: string;
  email?: string;
}
Enter fullscreen mode Exit fullscreen mode

In reality, it's dangerous.

Now every property could be undefined.

Instead, make only genuinely optional fields optional.

interface User {
  id: number;
  name: string;
  email?: string;
}
Enter fullscreen mode Exit fullscreen mode

7. Using Object Instead of Proper Types

Avoid:

function print(data: Object) {}
Enter fullscreen mode Exit fullscreen mode

or

let value: {};
Enter fullscreen mode Exit fullscreen mode

These types tell TypeScript almost nothing.

Prefer:

Record<string, unknown>
Enter fullscreen mode Exit fullscreen mode

or define a proper interface.

8. Not Typing API Responses

This is common:

const response = await fetch("/api/user");
const data = await response.json();
Enter fullscreen mode Exit fullscreen mode

Now data becomes any.

Instead:

interface User {
  id: number;
  name: string;
}

const data: User = await response.json();
Enter fullscreen mode Exit fullscreen mode

Even better, validate the response before trusting it.

Remember:

APIs can change.

Your types won't stop bad server responses.

9. Forgetting Generic Constraints

Without constraints:

function getId<T>(item: T) {
  return item.id;
}
Enter fullscreen mode Exit fullscreen mode

TypeScript correctly complains.

Use constraints:

function getId<T extends { id: number }>(item: T) {
  return item.id;
}
Enter fullscreen mode Exit fullscreen mode

Generics become much more useful when you define what they expect.

10. Fighting TypeScript Instead of Listening

Sometimes developers spend 20 minutes trying to silence an error.

Instead, ask:

Why is TypeScript complaining?

Often the compiler is pointing to:

  • incorrect assumptions
  • missing null checks
  • inconsistent object shapes
  • outdated interfaces

Many production bugs start as ignored TypeScript warnings.

Bonus Tip: Prefer Inference

You don't always need explicit types.

Instead of:

const name: string = "Alice";
Enter fullscreen mode Exit fullscreen mode

Simply write:

const name = "Alice";
Enter fullscreen mode Exit fullscreen mode

TypeScript already knows it's a string.

Use explicit annotations when they improve readability, not everywhere.

Final Thoughts

TypeScript isn't just a language, it's a safety net.

The more you work with it instead of around it, the more bugs it prevents before they ever reach production.

Quick Recap

✅ Avoid any
✅ Enable strict mode
✅ Don't abuse as
✅ Narrow union types
✅ Use interfaces and types appropriately
✅ Validate API responses
✅ Constrain generics
✅ Trust TypeScript's compiler

Conclusion

TypeScript is more than just a superset of JavaScript—it's a tool that helps you write reliable, maintainable, and scalable code. By avoiding these common mistakes, you'll spend less time debugging and more time building great applications.

Whether you're developing a small personal project or a large-scale web development application, following TypeScript best practices can significantly improve your code quality and developer experience.

Remember, TypeScript isn't there to slow you down—it's there to catch mistakes before your users do. Treat compiler errors as helpful feedback, not obstacles, and you'll build more robust software with greater confidence.

If you found this article helpful, share it with your fellow developers and let me know in the comments: Which TypeScript mistake has cost you the most time, and what lesson did you learn from it?

The goal isn't to make TypeScript happy.

The goal is to make your code safer, easier to refactor, and far easier to maintain.

What TypeScript mistake has cost you the most time? Share it in the comments, someone else will probably learn from it too.

Top comments (0)