Types and inference
TypeScript: any vs unknown vs never
In TypeScript, any disables checking, unknown makes you verify before use, and never marks impossible states. Learn the differences with examples.
TypeScript has three special types that sound similar and behave very differently: any, unknown, and never. Pick the wrong one and you either throw away type safety or block code that is perfectly valid. Let’s look at each.
any turns off checking
any is the escape hatch. Any value fits into it, and TypeScript stops checking what you do with it afterwards.
We touched on this in the TypeScript introduction. My advice is to avoid any whenever you can. It removes most of the benefits you installed TypeScript for.
Here is the danger:
function parseConfig(raw: any) {
return raw.host.toUpperCase()
}
parseConfig({ host: 3000 })
This compiles without a single complaint. At runtime it crashes, because 3000 has no toUpperCase() method. TypeScript saw raw.host and shrugged: it is any, so anything goes.
any feels like an easy way out. You pay for it later, usually in production.
unknown is the safe alternative
unknown also accepts any value. The difference is on the reading side: you must narrow it before you use it. TypeScript will not let you call a method or read a property on an unknown value until you have proved what it is.
This is exactly what you want for data you do not trust yet, like JSON from an API:
function parseJson(text: string): unknown {
return JSON.parse(text)
}
const data = parseJson('{"name":"Flavio"}')
// data.name // error TS18046: 'data' is of type 'unknown'.
if (typeof data === 'object' && data !== null && 'name' in data) {
console.log((data as { name: string }).name)
}
The direct data.name fails. Inside the if, we have checked that data is an object, that it is not null, and that it has a name property. Only then do we read it.
unknown forces you to check first. You keep the flexibility of accepting anything, without giving up safety.
If you come from JavaScript, how to check types covers the runtime checks that pair well with unknown.
never for impossible states
never is the opposite end. It means “no value can exist here”.
One use is a function that never returns normally, because it always throws:
function fail(message: string): never {
throw new Error(message)
}
The bigger win is exhaustive checks in a switch. Say we have a union of shapes and a function that handles each one:
type Shape =
| { kind: 'circle', radius: number }
| { kind: 'square', size: number }
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2
case 'square':
return shape.size ** 2
default:
const _exhaustive: never = shape
return _exhaustive
}
}
In the default branch, every case has been handled, so shape has type never and the assignment is fine. Now add a { kind: 'triangle', base: number } variant to Shape and forget the case. The compiler complains:
error TS2322: Type '{ kind: "triangle"; base: number; }' is not assignable to type 'never'.
That is the compiler telling you which branch you missed. I use this pattern in every switch over a union.
Quick comparison
any: no checking. Use rarely, and know why.unknown: any value goes in, but you must narrow before use. My default for untrusted data.never: no value can exist here. Use for functions that always throw and for exhaustive switches.
When you need reusable logic without any, generics are the better tool. We get there later in this course.
Lesson completed