cf.completefrontend
Open lab My progress
THE TYPESCRIPT FIELD GUIDE

any, unknown & never

Learn how any turns TypeScript's checks off, why unknown makes you check a value before using it, and where never appears, with server replies you can edit.

By the end, you can
  • 01
    Spot what any switches offExplain why a value typed any gets no checks, where any comes from, and how noImplicitAny stops the any you didn't write.
  • 02
    Use unknown safelyStore any value as unknown, then check it with typeof before you use it, like a saved setting or a caught error.
  • 03
    Read neverRecognize never as the type with no values: a function that always throws, a check with nothing left, an empty list.

any, unknown, and never: three special types

Most types describe one kind of value: number, string, an object with a price. Three special types describe something else. any and unknown are for a value that could be anything, like a reply from a server. never is for a value that can't exist at all. Here is a server reply typed as any, with a typo:

A reply typed any, with a typoPop out in the code editor (opens in a new tab)TypeScript
const reply: any = JSON.parse('{ "name": "Tea", "price": 3 }');console.log(reply.prise);

Line 1 turns the text from the server into an object with JSON.parse (the JSON lesson covers it) and says it's any. Line 2 reads reply.prise, a misspelling of price. TypeScript reports nothing, and the code prints undefined. any means "don't check this value", so TypeScript doesn't. Now the same two lines with unknown:

The same reply typed unknown (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
const reply: unknown = JSON.parse('{ "name": "Tea", "price": 3 }');console.log(reply.prise);

The only change is the type on line 1. Now TypeScript marks line 2, on purpose: 'reply' is of type 'unknown'. In plain words: "you haven't checked what reply is, so you can't read anything from it yet". unknown also accepts any value, but it makes you check the value before you use it. The code still prints undefined if you run it, because the types are removed before it runs.

Three boxes. The any box holds the cards 3 and Tea, and you can use it any way. The unknown box holds the same cards, and you must check it first. The never box is empty: nothing goes in.anything goes in3Teaanyuse it any way,no checksanything goes in3Teaunknowncheck it first,then use itnothing goes inneverno valueever fits
any and unknown both accept every value. With any you use it with no checks; with unknown you check it first. never accepts no value at all.
Real-life analogyJars in a kitchen

In a kitchen, a jar labelled "whatever you like" tempts you to pour without looking, and one day the cake gets salt. A jar with no label at all is safer: you open it and smell it first, and only then decide it's sugar. And some shelves have a spot where no jar can ever stand, because a pipe runs through it.

In real life: A jar labelled "whatever you like", poured without looking
In TypeScript: A value typed any, used with no checks
In real life: A jar with no label, opened and smelled before you cook
In TypeScript: A value typed unknown, checked with typeof before you use it
In real life: A spot on the shelf where no jar can ever stand
In TypeScript: The type never: no value fits
In real life: Salt in the cake, because nobody looked
In TypeScript: A typo that any lets through to a shopper
The main idea

any switches checking off: you can do anything with the value, and TypeScript reports nothing. unknown means "check first": it takes any value, but you must check what it is before you use it. never is the type with no values: it marks code that can't be reached.

any turns checking off

A value typed any gets no checks at all. You can read any property, call any method, and store it anywhere. TypeScript assumes you know what you're doing. Here is a product typed any:

A typo that any lets throughPop out in the code editor (opens in a new tab)TypeScript
const product: any = { name: "Tea", price: 3 };console.log(product.price.toFixed(2));console.log(product.prise.toFixed(2));

Line 1 makes a product with a name and a price, and says it's any. Line 2 reads the price and formats it with two decimals: it prints 3.00. Line 3 has the typo prise. TypeScript reports nothing, so the first sign of the bug is when the code runs: product.prise is undefined, and calling toFixed on it throws a TypeError that stops the code.

You don't have to write any to get it. Some functions give it back, and the best-known one is JSON.parse: TypeScript can't know what the text holds. Worse, anything you read from an any is any too:

any spreads to everything it touchesPop out in the code editor (opens in a new tab)TypeScript
const reply = JSON.parse('{ "name": "Tea", "price": 3 }');//    ^?const price = reply.price;//    ^?const quantity: number = reply.name;console.log(price, quantity);

Line 1 parses a reply and writes no type. The query on line 2 shows TypeScript gave it const reply: any. Line 3 reads reply.price, and the query on line 4 shows const price: any: still any, still unchecked. Line 5 stores reply.name, the text "Tea", in a variable that must be a number, and there's no error. Line 6 prints 3 Tea: a "number" holding text.

A box called reply holds an any card. An arrow labelled .price leads to a box called price, which also holds an any card. A note says: any again, nothing checked.anyreplyline 1.priceanypriceline 3any again,nothing checked
JSON.parse gives back any, so reply is any on line 1. Reading reply.price on line 3 gives any too, so nothing about price is checked either.

So one any can quietly switch checking off for every value that comes from it. The reply from fetch has the same problem: await response.json() gives back any too (the Fetch lesson covers fetch). Use any rarely, for example for a moment while you move old JavaScript to TypeScript.

unknown: check a value before you use it

unknown is the safe type for a value you don't know yet. Like any, it accepts every value:

Any value can go into an unknownPop out in the code editor (opens in a new tab)TypeScript
let saved: unknown = 3;saved = "dark";saved = { theme: "dark" };saved = null;console.log(saved);

Line 1 stores the number 3 in saved. Lines 2 to 4 store text, an object, and null. There are no errors, and line 5 prints null, the last value stored. The difference shows up when you try to use the value:

Using an unknown before checking it (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
const saved: unknown = JSON.parse('"dark"');console.log(saved.toUpperCase());const theme: string = saved;

Line 1 reads a saved theme. '"dark"' is the JSON for the text "dark", the way it would be stored in the browser. TypeScript marks two lines, on purpose:

  • Line 2: 'saved' is of type 'unknown'. You can't call a method on saved until you've checked what it is.
  • Line 3: Type 'unknown' is not assignable to type 'string'. You can't put it into a string variable either, because it might not be text.

To use it, check what it is with typeof, which gives the name of a value's type as text, like "string" or "number" (the Checking types lesson covers it):

Check that it's text, then use itPop out in the code editor (opens in a new tab)TypeScript
const saved: unknown = JSON.parse('"dark"'); if (typeof saved === "string") {  console.log(saved.toUpperCase());  //          ^?}

Line 3 asks whether saved is text. Inside the if, TypeScript knows it is: the query on line 5 shows const saved: string. So line 4 may call toUpperCase, and it prints DARK. Making a type more exact with a check like this is called narrowing; the Narrowing lesson covers it in depth.

A box called saved holds an unknown card. An arrow leads through a card that says typeof saved === "string" to a second box called saved, which holds a string card. Notes: before the check you cannot use it; inside the if it is a string.unknownsavedcan't use it yettypeof saved === "string"the check, line 3stringsavedinside the if
Before the check, saved is unknown and you can't use it. The check on line 3 only lets text through, so inside the if, saved is a string.

Not every check tells TypeScript enough. Try four different checks on line 3, and watch the list under the code:

Playground: pick the check before you use it
A saved theme, checked firstPop out in the code editor (opens in a new tab)TypeScript
const saved: unknown = JSON.parse('"dark"'); if (typeof saved === "string") {  console.log(saved.toUpperCase());}
Console outputDARK
What happens
Line 3 askstypeof saved === "string"
TypeScriptNo errors
Try it yourself

typeof saved === "string" is true only for text, so inside the if TypeScript treats saved as a string. toUpperCase is a string method, so there is no error, and the code prints DARK.

TypeScript checks the code for real each time you pick a check. Each button changes only line 3. Reset goes back to Check for text.

Only a check that proves saved is text lets line 4 call toUpperCase. Only check it's there rules out null and undefined but leaves numbers in. Check for a number proves the wrong type, and No real check proves nothing. Every choice except Check for a number prints DARK when it runs, because this saved value happens to be text; TypeScript asks you to be sure.

When you do know the shape of a value, you can give it a real type instead. Line 1 below gives an object type a name, Product, with the word type, so line 3 can use it (the Type aliases & interfaces lesson covers type). Compare the three types for the same reply:

Playground: give the reply a type
One server reply, three typesPop out in the code editor (opens in a new tab)TypeScript
type Product = { name: string; price: number }; const reply: any = JSON.parse('{ "name": "Tea", "price": 3 }');console.log(reply.price * 2);console.log(reply.prise);
Console output6, undefined
What happens
reply isany
TypeScriptNo errors
Try it yourself

With any, TypeScript checks nothing. Line 4 is right and line 5 has a typo, and neither gets an error. The typo prints undefined when the code runs.

TypeScript checks the code for real each time you pick a type. Each button changes only line 3. Reset goes back to any.

With any, nothing gets an error, not even the typo on line 5. With unknown, both lines 4 and 5 get errors until you check the reply. With Product, only the typo gets an error, and TypeScript even suggests price. The console output is the same every time, because the types are removed before the code runs. A type like Product is a promise you make: TypeScript can't see inside the text JSON.parse reads, so it trusts you.

never: the type with no values

never is the opposite of unknown. unknown accepts every value; never accepts none. You meet it in three places. The first is a function that never finishes normally, because it always throws:

A function that never returnsPop out in the code editor (opens in a new tab)TypeScript
function fail(message: string): never {  throw new Error(message);} function checkQuantity(quantity: number) {  if (quantity < 1) fail("Pick at least 1");  return quantity;} console.log(checkQuantity(2));

Line 1 says fail returns never: no call to it ever comes back with a value. Line 2 throws an error, which stops the function (the try and catch lesson covers throwing). Line 6 calls fail when the quantity is less than 1, and line 7 returns the quantity otherwise. Line 10 prints 2. With checkQuantity(0), line 6 would throw Pick at least 1.

TypeScript doesn't always work this type out for you. Compare a function declaration and an arrow function that both always throw:

What TypeScript works out for a function that always throwsPop out in the code editor (opens in a new tab)TypeScript
function fail(message: string) {//       ^?  throw new Error(message);} const stop = (message: string) => {//    ^?  throw new Error(message);};

The query on line 2 shows function fail(message: string): void. void means "returns nothing useful". The query on line 7 shows const stop: (message: string) => never. For a function declaration, TypeScript says void even when it always throws, so write : never yourself when you mean it.

The second place: nothing can ever be stored in a never.

Nothing can be stored in never (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
let leftover: never;leftover = 3;leftover = "Tea";

Line 1 makes a variable of type never. Lines 2 and 3 try to store the number 3 and the text "Tea", and both get an error on purpose: Type '3' is not assignable to type 'never'. The '3' in the message is the value itself. No value fits.

The third place is the most useful: never is what's left after checks have ruled out every possible type. This example uses string | number, which means "a string or a number". The next lesson, Union types, covers that |.

After every check, nothing is leftPop out in the code editor (opens in a new tab)TypeScript
function toQuantity(value: string | number) {  if (typeof value === "number") return value;  if (typeof value === "string") return Number(value);  return value;  //     ^?} console.log(toQuantity("2") + toQuantity(3));

Line 1 says value is a string or a number, like a quantity that may come from a form as text. Line 2 returns it when it's a number. Line 3 turns text into a number and returns that. So by line 4, no kind of value is left: the query on line 5 shows (parameter) value: never. Line 4 can never run. Line 7 prints 5, from 2 plus 3. The Exhaustiveness checking lesson uses this to make sure every case is handled.

A card string | number goes into a check, is it a number?, where numbers leave downwards. The rest goes into a second check, is it text?, where text leaves downwards. Nothing reaches the last card, never.valuestring | numberline 2is it a number?numbers returnline 3is it text?text returnsline 4nevernothingleft
value starts as a string or a number. Line 2 returns the numbers, and line 3 returns the text. On line 4 no kind of value is left, so its type is never.

You'll also meet never where you don't expect it. An empty list inside an object, with no type written, becomes a list of never. (An empty list stored straight in a variable is treated differently; the Arrays & tuples lesson shows that case.)

An empty list that can hold nothing (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
const cart = { items: [] };//    ^?cart.items.push("Tea");console.log(cart.items.length);

Line 1 starts a cart with no items. The query on line 2 shows items: never[]: TypeScript saw an empty list and nothing else, so it decided the list holds no values at all. Line 3 gets an error on purpose: Argument of type '"Tea"' is not assignable to parameter of type 'never', which means "this list can't hold "Tea"". The code still runs and line 4 prints 1. The fix is to say what the list will hold:

An empty list of stringsPop out in the code editor (opens in a new tab)TypeScript
const cart: { items: string[] } = { items: [] };cart.items.push("Tea");console.log(cart.items.length);

Line 1 says items is a string[], a list of strings, that starts empty. Line 2 adds "Tea" with no error, and line 3 prints 1.

noImplicitAny: no any you didn't write

When TypeScript can't work out a type and you didn't write one, it falls back to any. This is called an implicit any: an any you never asked for. The most common place is a function parameter:

A parameter with no type (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
function formatPrice(price) {  return "$" + price.toFixed(2);} console.log(formatPrice(3));

Line 1 takes a price but doesn't say what it is. TypeScript marks it on purpose: Parameter 'price' implicitly has an 'any' type. In plain words: "nothing says what price is, so it would be any and go unchecked". Line 5 prints $3.00 when it runs.

That error comes from a compiler setting called noImplicitAny. It's part of strict, the group of careful settings every example in these lessons uses. Here is the same function with noImplicitAny turned off, called with text:

The same function with noImplicitAny offPop out in the code editor (opens in a new tab)TypeScript
function formatPrice(price) {  return "$" + price.toFixed(2);} console.log(formatPrice("3"));

The list under the code shows the setting this example changes. There are no errors now: price is quietly any. So line 5 can pass the text "3", and when the code runs, line 2 throws TypeError: price.toFixed is not a function. Keep noImplicitAny on. Type the parameter yourself and watch the errors change:

Playground: type the parameter
A price formatterPop out in the code editor (opens in a new tab)TypeScript
function formatPrice(price) {  return "$" + price.toFixed(2);} console.log(formatPrice(3));
Line 1 takesprice
What happens
TypeScript1 error: line 1
When it runsprints $3.00
Try it yourself

Nothing says what price is, so TypeScript would have to treat it as any. With noImplicitAny on, it says Parameter 'price' implicitly has an 'any' type instead. "Implicitly" means you didn't write it.

The code is checked by the real TypeScript compiler each time you type. Only line 1 changes. Reset puts price back.

With price: number, the error is gone and price is checked. price: any also removes the error, because noImplicitAny only reports the any you didn't write. price: unknown moves the error to line 2, where the value is used. The table below sums it up. Read each row as: what you write, then what TypeScript says with the setting on and off.

A parameter with and without noImplicitAny
What you writeWith noImplicitAny onWith noImplicitAny off
function formatPrice(price)An error: Parameter 'price' implicitly has an 'any' type.No error. price is quietly any, and nothing about it is checked.
function formatPrice(price: any)No error. You wrote any yourself, so TypeScript allows it.No error, for the same reason.
function formatPrice(price: number)No error, and price is checked as a number.The same: no error, and price is checked as a number.

A saved setting and a caught error

A settings page saves the reader's font size in the browser as JSON text, and reads it back on the next visit. But saved text can be anything: an old version of the page, or a value someone edited by hand. Here is a reader that trusts it:

A saved font size read as anyPop out in the code editor (opens in a new tab)TypeScript
function readFontSize(saved: string): number {  const data = JSON.parse(saved);  return data;} const fontSize = readFontSize('"big"');console.log(fontSize + 2);

Line 1 promises that readFontSize returns a number. Line 2 parses the saved text, and data is any, so line 3 can return it with no error. Line 6 reads a saved value that is the text "big". Line 7 adds 2 and prints big2, and TypeScript said nothing. Now with unknown:

A saved font size read as unknown and checkedPop out in the code editor (opens in a new tab)TypeScript
function readFontSize(saved: string): number {  const data: unknown = JSON.parse(saved);  if (typeof data === "number") {    return data;  }  return 16;} console.log(readFontSize("18") + 2);console.log(readFontSize('"big"') + 2);

Line 2 says data is unknown. Without line 3, returning it would be an error, because an unknown can't go where a number must. Line 3 checks it's a number, and line 4 returns it. Anything else falls through to line 6, which returns the default size 16. Line 9 prints 20, from a saved 18. Line 10 prints 18: the saved "big" was replaced by 16.

You'll also meet unknown without writing it. In a catch block, the error is unknown, because JavaScript lets you throw any value, not just errors:

An error from a cart service (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
function loadCart() {  throw new Error("The cart service is down");} try {  loadCart();} catch (error) {  console.log(error.message);}

Line 2 throws an Error with a message. Line 6 calls it inside try, and line 7 catches what was thrown. Line 8 reads error.message, and TypeScript marks it on purpose: 'error' is of type 'unknown'. It runs and prints The cart service is down, but only because this code threw a real Error. The fix checks first:

Check that it's an Error, then read its messagePop out in the code editor (opens in a new tab)TypeScript
function loadCart() {  throw new Error("The cart service is down");} try {  loadCart();} catch (error) {  if (error instanceof Error) {    console.log(error.message);  }}

Line 8 uses instanceof Error, which is true when the value was made with new Error(…). Inside the if, error is an Error, so line 9 may read its message, and it prints The cart service is down. This setting is called useUnknownInCatchVariables, and it's part of strict too.

Error or fine?
  • let price: any = 3; price.toUpperCase();
  • let price: unknown = 3; price.toFixed(2);
  • let price: unknown = 3; price = "three";
  • let price: unknown = 3; const total: number = price;
  • let price: any = 3; const label: string = price;
  • const nothing: never = undefined;
  • function add(a, b) { return a + b; }
  • const value: unknown = 3; if (typeof value === "number") value.toFixed(2);
Try it yourself
0 of 8 correct

Sort each line by what TypeScript says about it. Each line is checked on its own, with strict on.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

  • "any and unknown are the same." No. Both accept every value, but any lets you use it with no checks, and unknown makes you check it first.
  • "No errors with any means the code is right." No. It means TypeScript didn't look. product.prise.toFixed(2) has no error and still throws.
  • "JSON.parse gives back unknown." No. It gives back any. Write const data: unknown = JSON.parse(text) to get the careful type.
  • "Any check makes an unknown usable." No. The check must prove the type you need. if (saved) leaves a number possible, so toUpperCase still gets an error.
  • "never is the same as void." No. void means a function finishes and returns nothing useful. never means it never finishes normally, because it always throws.
  • "Writing any fixes a noImplicitAny error." It removes the error, and the checking with it. Write the real type, like price: number, or unknown if you don't know it.

The table below puts the three types side by side. Read each row as a question, then each type's answer.

any, unknown, and never side by side
Questionanyunknownnever
Which values can you store in it?Every value.Every value.No value at all.
What can you do with it?Anything. TypeScript checks nothing.Nothing, until a check shows what it is.Nothing. Code that holds one can't be reached.
Can it go into a number variable?Yes, with no error, even when it's text.No. Check that it's a number first.Yes. That line can never run, so it's always allowed.
Where do you meet it?JSON.parse, response.json(), and an any you write.Values you choose not to trust yet, and the error in catch.Functions that always throw, checks with nothing left, and an empty [] inside an object with no type.

Practice exercises

Each exercise is TypeScript. When you fix code, press Edit & run on the starter code: the errors update as you type, and the console shows what it prints. Try a guess first, then use the hints.

Exercise 1 · Warm-upRead the type JSON.parse gives

No types are written here. What type does TypeScript give paid? Guess first. Then point at paid to check, or press Edit & run and add a // ^? line under it.

Starter codePop out in the code editor (opens in a new tab)TypeScript
const order = JSON.parse('{ "id": 7, "paid": false }');const paid = order.paid;

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upFind the line TypeScript marks

    A saved size, copied and then used. Which line will TypeScript mark?

    Starter codePop out in the code editor (opens in a new tab)TypeScript
    const saved: unknown = JSON.parse("16");
    const size = saved;
    console.log(size + 2);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeCheck a saved size before you use it

      TypeScript marks line 2. Add a check so the error goes away. What does the fixed code print?

      Starter codePop out in the code editor (opens in a new tab)TypeScript
      const saved: unknown = JSON.parse("16");console.log(saved.toFixed(1));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeFix two implicit anys

        Line 1 has two errors. Give both parameters a real type, not any. What does the fixed code print?

        Starter codePop out in the code editor (opens in a new tab)TypeScript
        function lineTotal(price, quantity) {  return price * quantity;} console.log(lineTotal(3, 2));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeFind the bug any hides

          TypeScript is happy with this order. What does it print? Then give the reply a real type and fix the bug it finds.

          Starter codePop out in the code editor (opens in a new tab)TypeScript
          const order: any = JSON.parse('{ "item": "Tea", "quantity": 2 }');console.log(order.quantaty * 3);

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeFix a list that can hold nothing

            Line 2 gets an error. What type did TypeScript give cart.items? Then fix the code so the cart can hold text.

            Starter codePop out in the code editor (opens in a new tab)TypeScript
            const cart = { items: [] };cart.items.push("Tea");console.log(cart.items.length);

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, keep two moments apart: what TypeScript says before the code runs, and what the code does when it runs. The quiz shows its code without the checker, so the error list doesn't give the answers away.

              The any, unknown & never quiz · 7 questionsScore: first tries count
              1. Question 1 of 7Which type turns TypeScript's checking off for a value?

                Choose an answer to see the explanation.

              2. Question 2 of 7What does TypeScript say about line 2 of this saved number?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                const data: unknown = JSON.parse("5");
                console.log(data * 2);

                Choose an answer to see the explanation.

              3. Question 3 of 7What does this typo on an any value print, and does TypeScript complain?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                const user: any = { name: "Asha" };
                console.log(user.nmae);

                Choose an answer to see the explanation.

              4. Question 4 of 7What return type does TypeScript work out for this function declaration that always throws?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                function fail(message: string) {
                  throw new Error(message);
                }

                Choose an answer to see the explanation.

              5. Question 5 of 7Which value can you store in a variable of type never?

                Choose an answer to see the explanation.

              6. Question 6 of 7With strict on, what does TypeScript report for this function?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                function double(amount) {
                  return amount * 2;
                }

                Choose an answer to see the explanation.

              7. Question 7 of 7What does this saved-value check print?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                const saved: unknown = JSON.parse('"16"');
                if (typeof saved === "number") {
                  console.log(saved + 2);
                } else {
                  console.log("Not a number");
                }

                Choose an answer to see the explanation.

              Key takeaways

              • any switches checking off: any property, any call, stored anywhere, with no errors.
              • any spreads: everything read from it is any too. JSON.parse and response.json() give it back.
              • unknown accepts every value, but you must check it, for example with typeof, before you use it.
              • The error in a catch block is unknown; check it with instanceof Error.
              • never has no values: a function that always throws, what's left after every check, an empty list inside an object with no type.
              • noImplicitAny, part of strict, reports an any you didn't write, like a parameter with no type.

              Remember the one-liner.
              any trusts everything, unknown checks first, and never holds nothing.

              Coming next: Union types. A value can be one of several types, like string | number, and you'll see how to work with it safely.

              CompleteFrontend Clear concepts. Working examples.