Command Palette

Search for a command to run...

Hectal
PHASE 1Beginner ~19 min· topic 6 of 7

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 any only 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. unknown and any are top types.
Escape hatch
A feature that lets you bypass the rules when you must. any is one.
Any leak
A value of type any entering 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.

implicit.tswhole filets
function greet(name) {
  return "Hi " + name.toUpperCase();
}
console.log(greet("Asha")); // Hi ASHA
console.log(greet(42));     // TypeError: name.toUpperCase is not a function

02any: 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.

unknown: nothing is allowed until you lookdiagram
Rendering diagram…

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.

narrow.tswhole filets
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. 1

    Narrow another kind

    In the third example add a branch if (typeof value === "boolean") { return value ? "yes" : "no"; }. Predict the type of value inside it. It is boolean.

  2. 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, both TS18046 ('data' is of type 'unknown'): one for each line that uses it. Check the Types panel.

  3. 3

    Silence on purpose

    In the first example put : any after name (name: any). The error goes away. Nothing about the program improved: the checker was just told to stop looking. That is the cost of any.

Code & diagrams

Implicit any: the parameter nobody typed

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 function
any versus unknown

Only 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 function
Narrowing unknown before use

Inside 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 else
An any leak from JSON.parse

JSON.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.

terminal
$ Types panel
── what you'll see ──
index.ts(6,13): error TS18046: 'safe' is of type 'unknown'.

Break #2

Leave a parameter untyped

In the first example, greet(name) has no annotation.

terminal
$ Types panel
── what you'll see ──
index.ts(1,16): error TS7006: Parameter 'name' implicitly has an 'any' type.

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.

  • ▸

    any is both a top type and a bottom-like type: everything is assignable to any, and any is assignable to everything except never. That two-way compatibility is why it disables checking. unknown is only a top type (assignable to unknown and any only).

  • ▸

    Prefer unknown in catch clauses (useUnknownInCatchVariables, part of strict since 4.4, Topic 13.3), generic defaults and for any function that takes "whatever". Make any rare enough to stand out in review: typescript-eslint's no-explicit-any and the no-unsafe-* rules find them (Topic 15.2).

  • ▸

    If you must silence one specific error, // @ts-expect-error is better than any: it applies to one line and tells you when the error goes away. Topic 10.6 compares it with @ts-ignore.

Remember this

  1. 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, even loose.toUpperCase() on a number. At runtime the program then crashes exactly as plain JavaScript would (you'll see TypeError: loose.toUpperCase is not a function below). any is how TypeScript lets old, untyped JavaScript in, and it is an escape hatch you should use rarely and on purpose.

  2. 2

    any spreads. A value computed from an any is usually any as well, so one careless any can 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) returns any, and so does response.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. 3

    Implicit any is any you didn't ask for, usually a parameter with no type: function greet(name). The flag **noImplicitAny** (part of strict, 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 adds any all 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. 4

    **unknown is 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 except unknown and any. 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. 5

    You turn an unknown into something useful by narrowing: checking its kind with typeof, instanceof or a custom guard. Inside if (typeof value === "string") the checker treats value as string, and value.toUpperCase() is fine. Outside it, you're back to unknown. Narrowing is the topic of Phase 4; here you need only the idea that unknown makes the check mandatory, and any makes it optional.

  6. 6

    The rule of thumb: when you truly don't know a value's type (parsed JSON, input from a form, a catch clause's error) use unknown and narrow or validate. Reach for any only 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: any also turns off checking *for everything touching it*; unknown doesn't.

Explain it without notes

01

What is the difference between any and unknown?

02

What is noImplicitAny and what does it catch?

03

How do you safely use a value of type unknown?

04

How can an any get into a TypeScript program without anyone writing the word any?

Practice

01

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.

02

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

  • ↔

    unknown costs 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 any can 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 any and unknown in one sentence each.

  • I can say what noImplicitAny catches and why it is on by default.

  • I can narrow an unknown with typeof to use it safely.

  • I can name two library calls that return any.

  • I can choose between unknown, any and a real type for a given situation.