DEV Community

Cover image for Don't Use "any" for Type-Safe TypeScript
Grenish rai
Grenish rai

Posted on

Don't Use "any" for Type-Safe TypeScript

The super power of TypeScript is that it enables detecting bugs during the compile process through strong, static typing. Using any might seem like the wanton way when you encounter a red wriggly, but it actually degrades precisely the advantage of TypeScript, type safety. Below we’ll look at why overusing any is a slippery slope, and what to do instead.

Why Avoid any?

Using any essentially tells TypeScript to skip type-checking for a variable, function, or object. This is potentially alluring when you are working with dynamic data or third party libraries that simply do not define all types, but it also has serious drawbacks:

  • Loss of Type Safety: any disables TypeScript’s ability to catch type-related errors at compile time, increasing the risk of runtime bugs.

  • Reduced Code Clarity: Code with any lacks clear intent, making it harder for developers to understand the expected data structure.

  • Maintenance Challenges: As projects grow, any can lead to brittle code that’s difficult to refactor or extend.

Let’s look at some examples to illustrate the pitfalls of using any and how to improve them with type-safe alternatives.

Examples

1. Unpredictable Function Behavior

Consider a function that processes user data:

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

If user is null, an object without a name property, or a completely different type, TypeScript won’t catch the error, and the code will fail at runtime. A type-safe alternative would be:

interface User {
  name: string;
}

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

Now, TypeScript ensures that user has a name property of type string. If you pass an invalid object, TypeScript will flag it during development:

processUser({ age: 30 }); // Error: Property 'name' is missing
Enter fullscreen mode Exit fullscreen mode

2. Working with APIs

When fetching data from an API, it’s tempting to use any for the response:

async function fetchData(): Promise<any> {
  const response = await fetch('https://api.example.com/data');
  return response.json();
}

fetchData().then(data => {
  console.log(data.items[0].title); // No guarantee `items` or `title` exists
});
Enter fullscreen mode Exit fullscreen mode

If the API response changes or returns unexpected data, this code could break at runtime. Instead, define an interface for the expected response:

interface ApiResponse {
  items: { title: string; id: number }[];
}

async function fetchData(): Promise<ApiResponse> {
  const response = await fetch('https://api.example.com/data');
  return response.json();
}

fetchData().then(data => {
  console.log(data.items[0].title); // TypeScript ensures `items` and `title` exist
});
Enter fullscreen mode Exit fullscreen mode

This approach guarantees that the code aligns with the API’s structure and catches mismatches early.

3. Third-Party Libraries

When using a library without type definitions, any might seem like the only option:

declare const someLibrary: any;

someLibrary.doSomething('hello');
Enter fullscreen mode Exit fullscreen mode

However, you can use unknown or create a basic type definition to maintain safety:

declare const someLibrary: {
  doSomething: (input: string) => void;
};

someLibrary.doSomething(123); // Error: Argument must be a string
Enter fullscreen mode Exit fullscreen mode

The unknown type is a safer alternative to any when you’re unsure of a value’s type, as it forces you to perform type checks before using it:

function processValue(value: unknown) {
  if (typeof value === 'string') {
    return value.toUpperCase();
  }
  throw new Error('Value must be a string');
}
Enter fullscreen mode Exit fullscreen mode

Best Practices for Type-Safe Code

  • Specific Types or Interfaces: You may define specific types (or interfaces) of your data structures, to make it clear and safe.
  • Use unknown Over any: unknown also triggers type checks in input data that is not fully known; this leaves TypeScript with its advantages.
  • Declare Library Types: Type definitions of third-party libraries should be declared even at the e
  • U*se Type Guards and Assertions*: Because handles dynamic data, the type is reduced safely using a type guard or assertion.

Conclusion

any type may be convenient in the short-term, but it loses the main advantages of TypeScript: type safety, clarity, and maintainability. Specific types, unknown, and TypeScript built-in stability features also allow you to create better reliable and readable code. Also, type safety has benefits of catching errors early and establishing confidence in quality application building.

Top comments (0)