DEV Community

Cover image for Stop using any. Use unknown instead
Rocky Chowdhury
Rocky Chowdhury

Posted on

Stop using any. Use unknown instead

TypeScript's main job is to catch type bugs at compile time, before your code ever runs. But there are two types designed for the "I don't know the type yet" situation — any and unknown — and they behave very differently. One opts you out of the type system entirely. The other keeps you safe.


The problem with any

When you annotate something as any, you're telling TypeScript: "Trust me, I know what I'm doing — stop checking." TypeScript steps aside and lets you do whatever you want with that value, with zero verification.

function parseInput(input: any) {
  return input.toUpperCase();
}

parseInput(21); // No compile error — but crashes at runtime
// TypeError: input.toUpperCase is not a function
Enter fullscreen mode Exit fullscreen mode

TypeScript doesn't check that 21 is a number, not a string. It happily compiles the code. The crash happens at runtime — silently defeating the entire purpose of using TypeScript in the first place.

any doesn't mean "flexible." It means "no type safety here." Every any is a hole in the type system through which bugs can silently enter.


The safer alternative: unknown

unknown is TypeScript's way of saying: "This value could be anything — and you must prove what it is before you use it." You cannot call methods or access properties on an unknown value without first narrowing its type.

function parseInput(input: unknown) {
  return input.toUpperCase();
  // Error: Object is of type 'unknown'
  // TypeScript catches this at compile time
}
Enter fullscreen mode Exit fullscreen mode

With unknown, the bug is caught before it ever reaches production. TypeScript forces you to narrow the type before doing anything with the value.


Type narrowing

Type narrowing is the mechanism TypeScript uses to refine an unknown or union type down to something specific. You perform a runtime check — a type guard — and TypeScript adjusts the inferred type within each branch of that check. The most common tools are typeof, instanceof, and custom type guard functions.

Using typeof

function formatValue(value: unknown): string {
  if (typeof value === "string") {
    return value.toUpperCase(); // TypeScript knows it's a string here
  }
  if (typeof value === "number") {
    return value.toFixed(2);   // TypeScript knows it's a number here
  }
  return "Unsupported type";
}
Enter fullscreen mode Exit fullscreen mode

Each if block narrows the type. Outside those blocks, value is still unknown. Inside them, TypeScript knows exactly what it is and unlocks the appropriate methods.

Using instanceof

instanceof is useful when working with class instances — most commonly when handling errors, which are typed as unknown in modern TypeScript.

function handleError(error: unknown): string {
  if (error instanceof Error) {
    return error.message; // Safe — TypeScript knows it's an Error object
  }
  return "Something went wrong";
}
Enter fullscreen mode Exit fullscreen mode

Without the instanceof check, accessing error.message would be a compile error. The check is the proof TypeScript needs.

Custom type guards

For complex objects, you can write a custom type guard function using the value is Type return syntax:

interface ApiResponse {
  status: number;
  data:   string;
}

function isApiResponse(value: unknown): value is ApiResponse {
  return (
    typeof value === "object" &&
    value !== null &&
    "status" in value &&
    "data" in value
  );
}

function handleResponse(value: unknown) {
  if (isApiResponse(value)) {
    console.log(value.status); // TypeScript knows the full shape here
  }
}
Enter fullscreen mode Exit fullscreen mode

Whenever you reach for any, ask yourself: is this actually "I don't know the type" or is it "I don't want to deal with the type right now"? The first case calls for unknown with narrowing. The second is technical debt. unknown keeps the compiler working for you — any sends it home early.

Top comments (0)