Topic 1.6
any, unknown and Turning Off Safety
In one line
any switches type checking off for a value, and the switch spreads. unknown also means "could be anything", but forces you to check what it is before you use it, so the safety stays on.
Think of it like this
Two kinds of mystery parcel. With the first, the delivery person tells you "don't worry, it's fine, use it any way you like": you open it with a hot knife and it turns out to be a balloon. With the second, you are told "unknown contents: look inside before you do anything". **any is the first parcel; unknown is the second.** Both mean "we don't know what's in it", but only one makes you look.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- any
- A type that turns type checking off for a value. Anything goes, and the checker stays silent.
- unknown
- A type for a value you know nothing about. You must check what it is before using it.
- Implicit any
- A value that became
anyonly because no type was given and none could be worked out. - noImplicitAny
- A compiler flag (part of
strict) that turns implicit any into an error. - Narrowing
- Checking a value's kind so the checker treats it as a more specific type inside that check.
- Top type
- A type that every value belongs to.
unknownandanyare top types. - Escape hatch
- A feature that lets you bypass the rules when you must.
anyis one. - Any leak
- A value of type
anyentering the program from a library, switching checks off downstream.
Step by step
01The missing label
A parameter with no type is any by default. With noImplicitAny on, TypeScript refuses and asks for a type (TS7006).
The hover shows name: any. Calling the function with a number compiles under any and crashes at runtime.
function greet(name) {
return "Hi " + name.toUpperCase();
}
console.log(greet("Asha")); // Hi ASHA
console.log(greet(42)); // TypeError: name.toUpperCase is not a function02any: everything is allowed
A variable of type any can be assigned anything and then used any way. The checker has nothing to say.
That is the danger: the compiler's "all clear" is now meaningless for this value and everything derived from it.
03unknown: nothing is allowed until you look
A value of type unknown accepts anything on the way in, but allows nothing on the way out. safe.toUpperCase() is TS18046.
Assigning an unknown to a string variable is TS2322. An any is assignable to string with no complaint.
04Narrow with typeof
Test the kind, and inside the if the checker treats the value as that kind. After the branches, any remaining case is still unknown.
This is the same checking you would do by hand in JavaScript. TypeScript makes it required instead of optional.
function describe(value: unknown): string {
if (typeof value === "string") {
return value.toUpperCase(); // value: string
}
if (typeof value === "number") {
return value.toFixed(1); // value: number
}
return "something else";
}05Where any sneaks in
JSON.parse returns any. So does response.json(). Whatever you read from that value is also any, so typos pass.
Safer: store the result as unknown and narrow or validate it, or use a schema library (Topic 13.4).
Try it yourself
- 1
Narrow another kind
In the third example add a branch
if (typeof value === "boolean") { return value ? "yes" : "no"; }. Predict the type ofvalueinside it. It isboolean. - 2
Let the compiler catch the leak
In the fourth example change line 1 to
const data: unknown = JSON.parse('{"name":"Asha"}');. Expect two errors, bothTS18046('data' is of type 'unknown'): one for each line that uses it. Check the Types panel. - 3
Silence on purpose
In the first example put
: anyaftername(name: any). The error goes away. Nothing about the program improved: the checker was just told to stop looking. That is the cost ofany.
Code & diagrams
Strict mode reports the missing type. Without it, greet(42) would pass the checker and crash at runtime, as the Console shows. Add : string to the parameter and the second call becomes an error instead.
Console
(nothing was printed)Expected console output
Hi ASHA
Uncaught TypeError: name.toUpperCase is not a functionOnly the unknown line is reported. The any line was a bug too: it crashes first, which is why the console shows an uncaught TypeError. The checker was silent for the one that actually crashed.
Console
(nothing was printed)Expected console output
Uncaught TypeError: loose.toUpperCase is not a functionInside the if, value is string. The last four lines compare the two types: unknown refuses to flow into string, any flows anywhere. All three describe calls are safe, including null.
Console
(nothing was printed)Expected console output
HI 3.0 something elseJSON.parse returns any. The compiler accepts a text value stored in a number and a misspelled property. Change line 1 to const data: unknown = ... and both lines become errors.
Console
(nothing was printed)Expected console output
Uncaught TypeError: Cannot read properties of undefined (reading 'length')Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Use an unknown value directly
In the second example, safe.toUpperCase() uses an unknown as if it were text.
Break #2
Leave a parameter untyped
In the first example, greet(name) has no annotation.
Myth vs fact
Myth
any and unknown are nearly the same.
Fact
any turns checking off and spreads. unknown keeps checking on and forces you to prove the kind before use. They are very different in safety.
Myth
Using any once is harmless.
Fact
It spreads: anything derived from an any is often any too, and the compiler reports nothing wrong on the way. A single any from JSON.parse can hide an entire module's worth of bugs.
Myth
unknown is annoying and any is practical.
Fact
The narrowing unknown demands is the same check JavaScript needs anyway. any just skips it until runtime.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
anyis both a top type and a bottom-like type: everything is assignable toany, andanyis assignable to everything exceptnever. That two-way compatibility is why it disables checking.unknownis only a top type (assignable tounknownandanyonly). - ▸
Prefer
unknownincatchclauses (useUnknownInCatchVariables, part ofstrictsince 4.4, Topic 13.3), generic defaults and for any function that takes "whatever". Makeanyrare enough to stand out in review: typescript-eslint'sno-explicit-anyand theno-unsafe-*rules find them (Topic 15.2). - ▸
If you must silence one specific error,
// @ts-expect-erroris better thanany: it applies to one line and tells you when the error goes away. Topic 10.6 compares it with@ts-ignore.
Remember this
- 1
The problem this solves: Sometimes you really don't know what a value is, such as data from outside the program. You need a safe way to say so, not a way to switch the checks off. **
any** is a type that turns the checker off for that value. You can call it, read any property from it, add it to a number, assign it to any other type, and nothing is reported, evenloose.toUpperCase()on a number. At runtime the program then crashes exactly as plain JavaScript would (you'll seeTypeError: loose.toUpperCase is not a functionbelow).anyis how TypeScript lets old, untyped JavaScript in, and it is an escape hatch you should use rarely and on purpose. - 2
anyspreads. A value computed from ananyis usuallyanyas well, so one carelessanycan switch off checking for a whole chain of code. The most common source is not a type you wrote, but a library function that returns it:JSON.parse(text)returnsany, and so doesresponse.json(). That is the "any leak": the types after it look checked, and nothing is. Fix leaks right where the data enters (Topic 13.4). - 3
Implicit any is
anyyou didn't ask for, usually a parameter with no type:function greet(name). The flag **noImplicitAny** (part ofstrict, so on by default in new projects) turns every case into an error (TS7006) so a missing type is never silent. Turning it off silently addsanyall over a project, which is why it is the first flag to switch on when you migrate old code, covered in Topic 15.4. - 4
**
unknownis the safe cousin: the type of a value you know nothing about. Anything can be assigned to**unknown(a number, a string,null, an object), but you can do almost nothing with it: no property reads, no calls, no arithmetic, and it is not assignable to any other type exceptunknownandany. The compiler says'safe' is of type 'unknown'(TS18046) until you check what the value really is. Because it contains every possible value, it is called a top type (Topic 5.4). - 5
You turn an
unknowninto something useful by narrowing: checking its kind withtypeof,instanceofor a custom guard. Insideif (typeof value === "string")the checker treatsvalueasstring, andvalue.toUpperCase()is fine. Outside it, you're back tounknown. Narrowing is the topic of Phase 4; here you need only the idea thatunknownmakes the check mandatory, andanymakes it optional. - 6
The rule of thumb: when you truly don't know a value's type (parsed JSON, input from a form, a
catchclause's error) useunknownand narrow or validate. Reach foranyonly when no safer type can express what you need and say why in a comment. Both are the "top" of the system and differ in one way:anyalso turns off checking *for everything touching it*;unknowndoesn't.
Explain it without notes
What is the difference between any and unknown?
What is noImplicitAny and what does it catch?
How do you safely use a value of type unknown?
How can an any get into a TypeScript program without anyone writing the word any?
Practice
Write function shout(value: unknown): string that returns the text upper-cased when it receives a string, and the text not text for everything else. Call it with "hi" and 42.
This function compiles but crashes. Change the type of data so TypeScript reports the problem, then fix the code.
``ts
const data = JSON.parse('{"name":"Asha"}');
console.log(data.nmae.length);
Trade-offs
- ↔
unknowncosts a few lines of checking at every boundary. That is the price of knowing what is inside, and cheaper than the production bug. - ↔
A strict ban on
anycan slow migration of old code. A reasonable path: allow it with a comment explaining why, track the count, and shrink it over time (Topic 15.4).
Done when you can
I can explain
anyandunknownin one sentence each.I can say what
noImplicitAnycatches and why it is on by default.I can narrow an
unknownwithtypeofto use it safely.I can name two library calls that return
any.I can choose between
unknown,anyand a real type for a given situation.