any, unknown & never

TypeScript · Lesson 12 of 28 6 min read Updated

Most types describe real values: strings, numbers, objects. TypeScript also has three special types that describe something else. any means "do not check this". unknown means "this could be anything, so check before you use it". never means "this cannot happen".

Knowing these three well separates beginners from confident TypeScript developers. In this lesson you will see why any is dangerous, how unknown makes outside data safe, and how never helps the compiler find missing cases.

any: the escape hatch

A value of type any turns off type checking. You can call any method, read any property and pass it anywhere, and TypeScript says nothing.

TypeScriptRun it yourself
let order: any = { item: "Paneer tikka", qty: 2 };

console.log(order.item.toUpperCase()); // fine
console.log(order.quantity);           // typo, no error from TypeScript

const price: number = order.item;      // a string sneaks into a number
console.log(price * 2);
Output
PANEER TIKKA
undefined
NaN

All three bugs would have been caught with a proper type. Worse, any spreads: the result of order.anything is also any, and assigning it to a number variable puts a string into a number without complaint. One any at the edge of your program can quietly disable checking far away from it.

Implicit any

In strict mode, TypeScript refuses to guess any for you. If it cannot work out a type, such as for an unannotated parameter, it reports an error:

TypeScript
function totalMarks(marks) {
  let sum = 0;
  for (const m of marks) sum += m;
  return sum;
}
Output
app.ts(1,21): error TS7006: Parameter 'marks' implicitly has an 'any' type.

This check is called noImplicitAny, and it is part of strict. The fix is to add a type: marks: number[]. Some values are any by design, for example the result of JSON.parse(), so watch for those too.

unknown: the safe "could be anything"

unknown also accepts any value, but it lets you do nothing with it until you prove what it is. It is the right type for data from outside your program: an API response, a file, localStorage, user input.

TypeScript
const data: unknown = JSON.parse('{"name": "Rohan", "marks": 91}');
console.log(data.name);

try {
  JSON.parse("{bad json");
} catch (err) {
  console.log(err.message);
}
Output
app.ts(2,13): error TS18046: 'data' is of type 'unknown'.
app.ts(7,15): error TS18046: 'err' is of type 'unknown'.

Notice the second error: in strict mode the variable in a catch block has type unknown, because JavaScript can throw any value, not only Error objects. To use an unknown value, narrow it with checks, just as you did with unions:

TypeScriptRun it yourself
type Student = { name: string; marks: number };

function isStudent(value: unknown): value is Student {
  return (
    typeof value === "object" &&
    value !== null &&
    "name" in value && typeof value.name === "string" &&
    "marks" in value && typeof value.marks === "number"
  );
}

function readStudent(json: string): string {
  let data: unknown;
  try {
    data = JSON.parse(json);
  } catch (err) {
    return err instanceof Error ? `Bad JSON (${err.name})` : "Bad JSON";
  }
  if (!isStudent(data)) {
    return "JSON is valid but not a student";
  }
  return `${data.name} scored ${data.marks}`;
}

console.log(readStudent('{"name": "Rohan", "marks": 91}'));
console.log(readStudent('{"name": "Rohan"}'));
console.log(readStudent("{bad json"));
Output
Rohan scored 91
JSON is valid but not a student
Bad JSON (SyntaxError)

The function isStudent is a type guard: its return type value is Student tells TypeScript that when it returns true, the value is a Student. After the check, data.name is allowed. You will write more of these in Type Guards & Assertions. In larger projects, validation libraries such as Zod or Valibot build these checks for you from a schema.

never: the impossible type

never is the type with no values at all. You meet it in three places.

First, a function that never finishes normally, because it always throws or loops forever, has the return type never:

TypeScriptRun it yourself
function fail(message: string): never {
  throw new Error(message);
}

function seatNumber(input: string): number {
  const n = Number(input);
  if (Number.isInteger(n) && n >= 1 && n <= 72) {
    return n;
  }
  return fail(`Invalid seat: ${input}`);
}

console.log(seatNumber("23"));
try {
  seatNumber("99");
} catch (err) {
  console.log(err instanceof Error ? err.message : err);
}
Output
23
Invalid seat: 99

Because fail returns never, TypeScript knows nothing after the call can run, and it accepts seatNumber as always returning a number.

Second, never appears when narrowing has removed every possibility. That is what makes the exhaustiveness check from Unions & Narrowing work: after all cases of a union are handled, the remaining type is never, and assigning a left-over case to never is an error.

Third, impossible combinations become never. For example, string & number is never, and never disappears from unions: string | never is just string. Advanced types use this to filter unions, as you will see in Conditional Types & infer.

void versus never

These two are easy to mix up:

TypeMeaningExample
voidThe function returns, but with no useful valuefunction log(msg: string): void
neverThe function never returns at allfunction fail(msg: string): never
undefinedA real value, undefinedlet x: undefined = undefined

Common mistakes

  • Using any to make an error go away. The error usually points to a real problem. Fix the type, or use unknown and narrow.
  • Trusting JSON.parse() and fetch().json(). Both return any. Assign the result to unknown and validate it.
  • Assuming a caught error is an Error. Check err instanceof Error before reading err.message.
  • Writing void for a function that always throws. Use never, so TypeScript knows code after the call is unreachable.

Key points

  • any disables type checking and spreads to everything it touches; avoid it.
  • Strict mode reports implicit any (error TS7006), for example on unannotated parameters.
  • unknown accepts any value but must be narrowed before use; use it for outside data and catch variables.
  • never has no values: it marks functions that never return, exhausted unions and impossible types.
  • void means "returns nothing useful"; never means "never returns".

Exercise

Write a function parsePrice(input: unknown): number for a shop's import script. It should accept a number (returned as is), or a string such as "₹1,299" or "499" (remove ₹ and commas, then convert). For anything else, or a string that is not a valid number, call a fail(message: string): never helper. Test it with 799, "₹1,299", "free" and null, catching the errors.

Show solution
TypeScript
function fail(message: string): never {
  throw new Error(message);
}

function parsePrice(input: unknown): number {
  if (typeof input === "number") return input;
  if (typeof input === "string") {
    const n = Number(input.replace(/[₹,\s]/g, ""));
    if (!Number.isNaN(n)) return n;
    return fail(`Not a price: "${input}"`);
  }
  return fail(`Unsupported value: ${String(input)}`);
}

for (const value of [799, "₹1,299", "free", null]) {
  try {
    console.log(parsePrice(value));
  } catch (err) {
    console.log(err instanceof Error ? err.message : err);
  }
}
// 799
// 1299
// Not a price: "free"
// Unsupported value: null

unknown forces the function to check the type before using string methods, and the never helper tells TypeScript that the function always returns a number or throws.

Quick Quiz

1. Which type lets you call any method without errors, even if it does not exist?

2. What must you do before reading a property of an unknown value?

3. What is the type of err in catch (err) with strict mode on?

4. What is the correct return type for a function that always throws?

Learn TypeScript with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call