any, unknown & never
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.
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);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:
function totalMarks(marks) {
let sum = 0;
for (const m of marks) sum += m;
return sum;
}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.
const data: unknown = JSON.parse('{"name": "Rohan", "marks": 91}');
console.log(data.name);
try {
JSON.parse("{bad json");
} catch (err) {
console.log(err.message);
}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:
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"));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:
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);
}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:
| Type | Meaning | Example |
|---|---|---|
void | The function returns, but with no useful value | function log(msg: string): void |
never | The function never returns at all | function fail(msg: string): never |
undefined | A real value, undefined | let x: undefined = undefined |
Common mistakes
- Using
anyto make an error go away. The error usually points to a real problem. Fix the type, or useunknownand narrow. - Trusting
JSON.parse()andfetch().json(). Both returnany. Assign the result tounknownand validate it. - Assuming a caught error is an
Error. Checkerr instanceof Errorbefore readingerr.message. - Writing
voidfor a function that always throws. Usenever, so TypeScript knows code after the call is unreachable.
Key points
anydisables type checking and spreads to everything it touches; avoid it.- Strict mode reports implicit
any(error TS7006), for example on unannotated parameters. unknownaccepts any value but must be narrowed before use; use it for outside data andcatchvariables.neverhas no values: it marks functions that never return, exhausted unions and impossible types.voidmeans "returns nothing useful";nevermeans "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
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: nullunknown 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?
any switches type checking off completely.
2. What must you do before reading a property of an unknown value?
unknown blocks every operation until you prove what the value is.
3. What is the type of err in catch (err) with strict mode on?
JavaScript can throw any value, so strict mode types catch variables as unknown.
4. What is the correct return type for a function that always throws?
never means the function never returns normally.
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
