10 Bad TypeScript Habits To Break In 2024

Safdar Ali - Oct 16 - - Dev Community

TypeScript has become the go-to language for many developers, providing the benefits of static typing while maintaining the flexibility of JavaScript. However, as TypeScript continues to evolve, some practices that were once considered best may now be outdated or suboptimal. In this article, we’ll cover 10 bad TypeScript habits you should break in 2024 to write cleaner, more efficient, and maintainable code.

1. Not Using strict Mode

The Problem:

Many developers disable TypeScript’s strict mode to avoid dealing with stricter type checks.

Why It’s Bad:

When strict mode is off, TypeScript becomes more lenient, reducing the effectiveness of type safety. This can lead to unexpected bugs and make refactoring more challenging in the future.

What to Do Instead:

Enable strict mode in your tsconfig.json. It will enforce better type checks and ensure that your code is more robust in the long run.

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

2. Relying on any Too Much

The Problem:

The any type is often used as a quick fix when developers don’t want to think about the correct type.

Why It’s Bad:

Using any bypasses the entire purpose of TypeScript, making it essentially JavaScript again. This can lead to runtime errors, as TypeScript is no longer able to help catch mistakes during development.

What to Do Instead:

Use more specific types like unknown, or better yet, define a proper type for your data. The unknown type is safer than any because it forces type-checking before using the value.

let data: unknown;

if (typeof data === "string") {
  console.log(data.toUpperCase());
}
Enter fullscreen mode Exit fullscreen mode

3. Using Type Assertions Carelessly

The Problem:

Type assertions (as keyword) can be overused, especially when developers are unsure about types and want to silence TypeScript errors.

Why It’s Bad:

Type assertions can lead to unsafe code because they tell TypeScript to treat a value as a specific type, even if it might not be. This can result in runtime issues if the value isn’t what you expect.

What to Do Instead:

Limit type assertions. Instead, use type guards, which allow you to safely check and infer types before using them.

function isString(value: unknown): value is string {
  return typeof value === 'string';
}

if (isString(data)) {
  console.log(data.toUpperCase());
}
Enter fullscreen mode Exit fullscreen mode

4. Neglecting Union and Intersection Types

The Problem:

Some developers avoid using union (|) and intersection (&) types, even when they would make the code more expressive and precise.

Why It’s Bad:

Without union and intersection types, your code can become overly verbose, and you might miss out on TypeScript’s ability to describe complex data structures more concisely.

What to Do Instead:

Leverage union and intersection types to create more flexible and reusable type definitions.

type Admin = { isAdmin: true; privileges: string[] };
type User = { isAdmin: false; email: string };

type Person = Admin | User;

function logUser(person: Person) {
  if (person.isAdmin) {
    console.log(person.privileges);
  } else {
    console.log(person.email);
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Using Non-Specific Return Types

The Problem:

Functions often have return types of any, object, or other non-specific types, leaving the consumer of the function to figure out what to expect.

Why It’s Bad:

Non-specific return types make your code less predictable and harder to debug. You lose the benefit of TypeScript’s static type checking, making your code more error-prone.

What to Do Instead:

Always specify the exact return type of a function. If the function returns multiple types, use union types to describe them.

function fetchData(): Promise<{ id: number; name: string }> {
  return fetch("/data").then(response => response.json());
}
Enter fullscreen mode Exit fullscreen mode

6. Ignoring null and undefined

The Problem:

Some developers ignore the presence of null and undefined values in their code, leading to unexpected errors.

Why It’s Bad:

JavaScript allows variables to be null or undefined, and TypeScript provides tools to handle these values explicitly. Ignoring them can lead to runtime errors when accessing properties or calling methods on null or undefined.

What to Do Instead:

Use optional chaining and the nullish coalescing operator to safely handle null and undefined values.

const name = user?.profile?.name ?? "Guest";
Enter fullscreen mode Exit fullscreen mode

7. Overusing Enums

The Problem:

Enums are often overused for simple constant values, when other types like const or literal unions could suffice.

Why It’s Bad:

Enums can add unnecessary complexity, especially when simpler alternatives exist. In some cases, they can even introduce issues related to mutability.

What to Do Instead:

Use literal types or const objects for simple constant values.

type Role = "Admin" | "User" | "Guest";

let userRole: Role = "Admin";
Enter fullscreen mode Exit fullscreen mode

8. Not Utilizing readonly

The Problem:

Failing to use the readonly keyword leads to mutable data structures, which can cause bugs and unintended side effects.

Why It’s Bad:

Mutability can lead to accidental modifications of objects or arrays, making it harder to trace bugs.

What to Do Instead:

Use readonly to enforce immutability where appropriate.

const data: readonly number[] = [1, 2, 3];
Enter fullscreen mode Exit fullscreen mode

9. Neglecting Custom Type Guards

The Problem:

Many developers rely on implicit checks rather than using custom type guards to ensure type safety.

Why It’s Bad:

Without explicit type guards, you risk missing certain types during runtime, leading to potential errors.

What to Do Instead:

Implement custom type guards to explicitly check types and make your code more reliable.

function isUser(user: any): user is User {
  return typeof user.email === "string";
}
Enter fullscreen mode Exit fullscreen mode

10. Not Taking Advantage of unknown Type

The Problem:

Developers often default to using any for variables when the type is initially unknown.

Why It’s Bad:

any disables type checking, which defeats the purpose of using TypeScript.

What to Do Instead:

Use the unknown type for variables when you’re not sure about the type initially, and narrow the type as needed.

let input: unknown;

if (typeof input === "string") {
  console.log(input.toUpperCase());
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Breaking these bad TypeScript habits in 2024 will help you write more maintainable, performant, and error-free code. By embracing stricter typing, avoiding shortcuts like any, and fully leveraging TypeScript’s advanced features, you can significantly improve your code quality and become a more effective developer.

That's all for today.

And also, share your favourite web dev resources to help the beginners here!

Connect with me:@ LinkedIn and checkout my Portfolio.

Explore my YouTube Channel! If you find it useful.

Please give my GitHub Projects a star ⭐️

Thanks for 32017! 🤗

This is a submission for the 2024 Hacktoberfest Writing challenge: Contributor Experience

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .