Question

TypeScript unknown vs any: Differences, Safety, and When to Use Each

typescripttypescript3.0

Question

In TypeScript, unknown was introduced as a built-in type in TypeScript 3.0. What is the difference between unknown and any? When should unknown be used instead of any?

For example, how do these types behave differently?

let valueAny: any = "hello";
let valueUnknown: unknown = "hello";

valueAny.toUpperCase();
valueUnknown.toUpperCase();

Short Answer

By the end of this page, you will understand how any and unknown differ in TypeScript, why unknown is safer, and when each type should be used. You will also see how unknown forces type checks before use, which helps prevent runtime errors in real applications.

Concept

any and unknown both represent values whose type is not known ahead of time, but they behave very differently.

  • any turns off type checking for that value.
  • unknown keeps the fact that the type is unknown, so TypeScript requires you to prove what it is before using it.

What any means

When a value is typed as any, TypeScript allows almost anything:

let value: any = "hello";
value.toUpperCase();
value.doesNotExist();
value();

TypeScript will not complain, even if the code is clearly unsafe. This makes any an escape hatch.

What unknown means

When a value is typed as unknown, TypeScript says:

"I know this value exists, but I do not know what it is yet."

Because of that, you cannot directly:

  • call methods on it
  • access properties on it
  • use it like a function
  • assign it to more specific types without checking
let value: unknown = "hello";
// value.toUpperCase(); // Error

You must narrow the type first:

let value: unknown = "hello";

if (typeof value === "string") {
  console.log(value.toUpperCase());
}

Why this matters

In real programs, you often receive data from places you do not fully control:

  • API responses
  • user input
  • JSON files
  • environment variables
  • third-party libraries

Using any tells TypeScript to trust the data immediately. Using unknown tells TypeScript to make you validate it first.

That is why unknown is usually the safer default when the actual type is not yet known.

Mental Model

Think of these two types like packages.

  • any is a package with no rules. You are allowed to open it, use it, or assume anything is inside.
  • unknown is a sealed package with a warning label: "Contents not verified." You must inspect it before using it.

With any, TypeScript steps aside and says, "You are on your own."

With unknown, TypeScript says, "I will help you stay safe until you confirm what this value is."

Take Quiz

Syntax and Examples

The main difference appears when you try to use the value.

Basic syntax

let a: any;
let b: unknown;

Example: method calls

let valueAny: any = "hello";
let valueUnknown: unknown = "hello";

console.log(valueAny.toUpperCase()); // Allowed
// console.log(valueUnknown.toUpperCase()); // Error

valueUnknown.toUpperCase() fails because TypeScript does not yet know that valueUnknown is a string.

Narrowing unknown

let valueUnknown: unknown = "hello";

if (typeof valueUnknown === "string") {
  console.log(valueUnknown.toUpperCase()); // Allowed
}

Example: assigning to other types

let valueAny: any = 42;
let valueUnknown: unknown = 42;

let num1: number = valueAny; // Allowed
// let num2: number = valueUnknown; // Error

unknown cannot be assigned to a more specific type until you check or assert it.

Using a type assertion

let valueUnknown: unknown = "hello";
let text = valueUnknown as string;
console.log(text.toUpperCase());

Type assertions work, but they should be used carefully. A runtime check is usually safer than just asserting.

Example: function input

function printLength(value: unknown) {
  if (typeof value === "string" || Array.isArray(value)) {
    console.log(value.length);
  } else {
    console.log("No length available");
  }
}

This is a common and safe pattern for working with unknown data.

Step by Step Execution

Consider this example:

function shout(input: unknown) {
  if (typeof input === "string") {
    return input.toUpperCase();
  }

  return "NOT A STRING";
}

console.log(shout("hello"));
console.log(shout(123));

Step by step

  1. The function parameter input is declared as unknown.
  2. That means the function accepts any value, but cannot use it unsafely.
  3. The if (typeof input === "string") check narrows input from unknown to string inside that block.
  4. Inside the block, input.toUpperCase() is allowed because TypeScript now knows input is a string.
  5. If the input is not a string, the function returns a fallback value.

What happens at runtime

For shout("hello"):

  • input is "hello"
  • the typeof check passes
  • toUpperCase() runs
  • result: "HELLO"

For shout(123):

  • input is 123
  • the typeof check fails
  • fallback is returned
  • result: "NOT A STRING"

This is exactly why unknown is useful: it forces you to handle uncertainty safely.

Real World Use Cases

API responses

When data comes from an external API, the shape may not be guaranteed.

async function fetchUser(): Promise<unknown> {
  const response = await fetch("/api/user");
  return response.json();
}

You can then validate the data before using it.

Parsing JSON

const raw = '{"name":"Ada"}';
const data: unknown = JSON.parse(raw);

JSON.parse() returns data that could be anything. Treating it as unknown is safer than trusting it immediately.

User input

Form values, query parameters, and CLI arguments often need validation before use.

Catching errors

In modern TypeScript, caught errors are often treated cautiously because they may not always be Error objects.

try {
  throw "Something went wrong";
} catch (error: unknown) {
  if (error instanceof Error) {
    console.log(error.message);
  } else {
    console.log("Unknown error", error);
  }
}

Library boundaries

When building reusable utilities or SDKs, unknown is useful at the edges of your system where input is not trusted yet.

Take Quiz

Real Codebase Usage

In real projects, unknown is commonly used at boundaries and any is avoided unless there is a strong reason.

Common patterns

1. Validation at the edges

Developers often accept unknown first, then validate.

function isUser(value: unknown): value is { name: string } {
  return (
    typeof value === "object" &&
    value !== null &&
    "name" in value
  );
}

2. Guard clauses

function greet(value: unknown) {
  if (typeof value !== "string") {
    return "Invalid name";
  }

  return `Hello, ${value}`;
}

This keeps unsafe values out of the main logic.

3. Error handling

function getErrorMessage(error: unknown): string {
  if (error instanceof Error) {
    return error.message;
  }
  return String(error);
}

4. Safer wrapper functions

A function may accept unknown, then return a strict type only after checks pass.

function parseCount(value: unknown): number {
  if (typeof value === "number") return value;
  if (typeof value === "string") return Number(value);
  return 0;
}

When any still appears

any is sometimes used:

  • during gradual migration from JavaScript to TypeScript
  • when a third-party library has poor typings
  • in quick prototypes
  • in highly dynamic code that is hard to model

Even then, many teams try to isolate any to small areas rather than spreading it through the codebase.

Common Mistakes

1. Using any as the default for unknown data

Broken example:

function printName(user: any) {
  console.log(user.name.toUpperCase());
}

This compiles, but can crash if user.name is missing or not a string.

Safer version:

function printName(user: unknown) {
  if (
    typeof user === "object" &&
    user !== null &&
    "name" in user &&
    typeof (user as { name: unknown }).name === "string"
  ) {
    console.log((user as { name: string }).name.toUpperCase());
  }
}

2. Forgetting to narrow unknown

Broken example:

let value: unknown = "hello";
// console.log(value.length); // Error

Fix:

if (typeof value === "string") {
  console.log(value.length);
}

3. Replacing checks with unsafe assertions

let value: unknown = 123;
console.log((value as string).toUpperCase());

This compiles, but may fail at runtime because the assertion does not change the real value.

4. Thinking unknown is useless because it blocks access

That restriction is the main benefit. It pushes validation into your code and catches unsafe assumptions early.

5. Overusing any in function parameters

function handle(data: any) {
  return data.items.map((x: any) => x.id);
}

This spreads unsafe typing through the whole function. Prefer validating once, then using proper types afterward.

Comparisons

Featureanyunknown
Can hold any valueYesYes
Can call methods without checksYesNo
Can access properties without checksYesNo
Can be assigned to other types freelyYesNo
Requires type narrowingNoYes
Type safetyVery lowHigh
Best use caseEscape hatch, migration, difficult typingUntrusted input, external data, safe boundaries

unknown vs object

  • unknown can be literally any value: string, number, object, null, etc.
  • object means a non-primitive object value.
let a: unknown = 5;
// Allowed

// let b: object = 5; // Error

unknown vs never

  • unknown means "could be anything"
  • never means "this cannot happen"

They are almost opposites in meaning.

Rule of thumb

  • Use unknown when you do not know the type yet.
  • Use any only when you intentionally want to skip type checking.
Take Quiz

Cheat Sheet

let x: any;
let y: unknown;

any

  • Disables type checking for that value
  • You can call methods, access properties, and assign it anywhere
  • Useful as a last resort
let value: any = "hello";
value.toUpperCase();
value.notReal(); // still allowed by TypeScript

unknown

  • Accepts any value
  • Cannot be used directly until narrowed
  • Safer than any
let value: unknown = "hello";

if (typeof value === "string") {
  value.toUpperCase();
}

Narrowing tools

  • typeof
  • instanceof
  • Array.isArray()
  • custom type guards
  • equality checks

Assignment rules

let a: any = 123;
let b: unknown = 123;

let n1: number = a; // OK
// let n2: number = b; // Error

Best practice

  • Prefer unknown for external or untrusted data
  • Use any only when you truly need an escape hatch

FAQ

Is unknown safer than any in TypeScript?

Yes. unknown is safer because it forces you to check the value before using it.

Should I always use unknown instead of any?

In most cases, yes when the type is not known yet. Use any only when you intentionally want to bypass type checking.

Why does unknown cause errors when I access properties?

Because TypeScript does not know whether that property exists. You must narrow the type first.

Can unknown hold a string, number, or object?

Yes. unknown can store any value.

How do I convert unknown into a specific type?

Use type narrowing with checks like typeof, instanceof, or a custom type guard. You can also use a type assertion, but that is less safe.

When is any acceptable?

It can be acceptable during migration, when working around bad library typings, or in small isolated areas where strict typing is not practical.

Is unknown the same as object?

No. unknown can be any value at all, while object only represents non-primitive object values.

Take Quiz

Mini Project

Description

Build a small TypeScript utility that safely reads unknown API-like data and extracts a user's name. This demonstrates the main value of unknown: accepting uncertain input, validating it, and only then using it as a trusted type.

Goal

Create a function that accepts unknown input and returns a safe user name string without risking invalid property access.

Requirements

  • Accept one input typed as unknown
  • Return the user's name if the input contains a valid string name property
  • Return a fallback string such as "Anonymous" when the input is invalid
  • Use runtime checks instead of relying only on type assertions
Take Quiz

Keep learning

Related questions

View more