Unknown vs Any: Safer Alternatives

Table of Contents

  • Introduction
  • What is the any Type?
  • What is the unknown Type?
  • Differences Between any and unknown
    • Type Safety
    • Type Checking
    • Type Inference
  • When to Use unknown Over any
  • How to Safely Work with unknown
  • Best Practices for Using unknown
  • Conclusion

Introduction

TypeScript is a statically typed language that offers developers the ability to catch errors at compile time. It provides powerful tools for managing types, but sometimes, especially when working with dynamic data, developers encounter situations where strict typing isn’t feasible. This is where TypeScript’s any and unknown types come into play.

Both any and unknown allow for flexibility when working with unknown or dynamic types. However, their purposes and how they function differ. In this article, we’ll compare any and unknown, explore their use cases, and discuss why unknown is considered a safer alternative to any.


What is the any Type?

In TypeScript, the any type is a special type that allows you to bypass the type system entirely. When a variable is typed as any, TypeScript doesn’t enforce any type checking for that variable, meaning it can hold any value without triggering errors. This flexibility can be useful in certain situations, but it comes with significant drawbacks, most notably the loss of type safety.

let value: any = "Hello, world!";
value = 10; // OK
value = true; // OK
value = { name: "John", age: 30 }; // OK

As shown above, any can hold values of any type, whether it’s a string, number, object, or any other type. While this might seem convenient, it defeats the purpose of TypeScript’s type system, which is designed to catch errors at compile time.


What is the unknown Type?

The unknown type is another special type in TypeScript, but it is more restrictive than any. When a variable is typed as unknown, TypeScript ensures that you don’t perform operations on the value unless you first check its type. This makes unknown a safer alternative to any because it maintains type safety by requiring explicit checks before you can use the value.

let value: unknown = "Hello, world!";
value = 10; // OK
value = true; // OK
value = { name: "John", age: 30 }; // OK

// Error: Object is of type 'unknown'.
let length = value.length;

In the example above, TypeScript does not allow us to directly access properties or perform operations on the unknown type value unless we first check its type. This is a safeguard against runtime errors.


Differences Between any and unknown

Here’s a detailed comparison of any and unknown based on key characteristics:

1. Type Safety

  • any: When you assign any to a variable, TypeScript essentially disables type checking for that variable. It allows you to perform operations on it without any restrictions.
  • unknown: With unknown, TypeScript requires you to perform type checking or type assertions before performing operations. This ensures that the value is safe to use in the context you need.

2. Type Checking

  • any: TypeScript won’t perform any checks on the value of a variable typed as any. You can assign any to another type without any issues, leading to potential type errors at runtime. let value: any = "Hello"; let num: number = value; // No type checking, no errors at compile time
  • unknown: TypeScript requires that you check the type of a variable before using it, enforcing safer programming practices. let value: unknown = "Hello"; if (typeof value === "string") { let length = value.length; // OK, TypeScript knows "value" is a string here }

3. Type Inference

  • any: The any type doesn’t provide any type inference, as the value can be anything. let value: any = "Hello"; value = 42; // No error, value can change to any type
  • unknown: With unknown, TypeScript can’t infer the operations you can perform on the value until you explicitly check or narrow the type. let value: unknown = "Hello"; // TypeScript prevents us from calling methods directly value.toUpperCase(); // Error: Object is of type 'unknown'

When to Use unknown Over any

unknown is generally considered a safer alternative to any because it enforces a stricter type system. Use unknown when you need to handle dynamic data and you want to ensure that the value is type-checked before performing operations on it. Some examples of when to use unknown over any include:

  • Handling dynamic data from external sources: When working with data from APIs or user input that can have an unpredictable structure, unknown allows you to handle it safely. function handleData(data: unknown) { if (typeof data === "string") { console.log(data.length); // OK: "data" is now known to be a string } }
  • Working with third-party libraries: If you’re integrating third-party JavaScript libraries or modules that do not have TypeScript type definitions, you might use unknown to type variables that hold the data from those libraries.
  • Gradual Migration from JavaScript: If you’re transitioning a JavaScript codebase to TypeScript, using unknown allows you to maintain type safety while gradually improving the typings.

How to Safely Work with unknown

While unknown enforces type safety, it requires you to check the type before performing operations on the value. There are several ways to safely handle unknown values:

1. Type Narrowing with typeof

TypeScript provides the typeof operator, which allows you to check the type of a variable and narrow it down to a specific type.

let value: unknown = "Hello";

if (typeof value === "string") {
console.log(value.length); // OK: TypeScript knows value is a string
}

2. Type Guards with Custom Functions

You can create custom type guard functions to narrow the type of a value based on more complex logic.

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

let value: unknown = "Hello";

if (isString(value)) {
console.log(value.length); // OK: value is now inferred as a string
}

3. Type Assertions

If you’re confident about the type of a value, you can use type assertions to tell TypeScript the type of the variable. However, you should use this sparingly as it bypasses type checking.

let value: unknown = "Hello";
let str = value as string; // Type assertion
console.log(str.length); // OK

Best Practices for Using unknown

  • Avoid Overuse of any: Try to avoid using any in your code as it undermines TypeScript’s type safety. If you must use a flexible type, prefer unknown instead, which enforces better type safety.
  • Always Perform Type Checking: With unknown, you must always check the type before performing operations. Use typeof, instanceof, or custom type guard functions to ensure the value is of the correct type.
  • Use Type Assertions Only When Necessary: Type assertions can be useful but should be used cautiously. Overusing type assertions can lead to runtime errors.
  • Start with unknown for Dynamic Data: If you’re dealing with dynamic or external data, start by using unknown to enforce type checking, and only switch to any as a last resort.

Conclusion

While both any and unknown provide flexibility in dealing with dynamic or uncertain data, unknown is the safer alternative. It ensures type safety by requiring explicit type checks before you can perform operations on the value. By using unknown, you retain TypeScript’s strong typing system while still handling dynamic data in a safe and controlled manner.

In contrast, any should be used sparingly, as it completely bypasses TypeScript’s type system, leading to potential runtime errors and harder-to-maintain code. Always prefer unknown unless you absolutely need the flexibility offered by any.

Remember, TypeScript is designed to help you catch errors early, and using unknown ensures that you’re still working within the constraints of the language’s type safety.