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.
- 01Spot 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.
- 02Use unknown safelyStore any value as unknown, then check it with typeof before you use it, like a saved setting or a caught error.
- 03Read 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:
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:
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.
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.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 withtypeofbefore 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
anylets through to a shopper
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:
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:
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.
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:
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:
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 onsaveduntil you've checked what it is. - Line 3:
Type 'unknown' is not assignable to type 'string'. You can't put it into astringvariable 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):
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.
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:
const saved: unknown = JSON.parse('"dark"'); if (typeof saved === "string") { console.log(saved.toUpperCase());}DARKtypeof saved === "string"No errorstypeof 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.
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:
type Product = { name: string; price: number }; const reply: any = JSON.parse('{ "name": "Tea", "price": 3 }');console.log(reply.price * 2);console.log(reply.prise);6, undefinedanyNo errorsWith 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.
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:
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:
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.
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 |.
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.
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.)
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:
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:
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:
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:
function formatPrice(price) { return "$" + price.toFixed(2);} console.log(formatPrice(3));price1 error: line 1prints $3.00Nothing 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.
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.
| What you write | With noImplicitAny on | With 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:
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:
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:
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:
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.
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);
Sort each line by what TypeScript says about it. Each line is checked on its own, with strict on.
Common mix-ups
- "
anyandunknownare the same." No. Both accept every value, butanylets you use it with no checks, andunknownmakes you check it first. - "No errors with
anymeans the code is right." No. It means TypeScript didn't look.product.prise.toFixed(2)has no error and still throws. - "
JSON.parsegives backunknown." No. It gives backany. Writeconst data: unknown = JSON.parse(text)to get the careful type. - "Any check makes an
unknownusable." No. The check must prove the type you need.if (saved)leaves a number possible, sotoUpperCasestill gets an error. - "
neveris the same asvoid." No.voidmeans a function finishes and returns nothing useful.nevermeans it never finishes normally, because it always throws. - "Writing
anyfixes anoImplicitAnyerror." It removes the error, and the checking with it. Write the real type, likeprice: number, orunknownif 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.
| Question | any | unknown | never |
|---|---|---|---|
| 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.
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.
const order = JSON.parse('{ "id": 7, "paid": false }');const paid = order.paid;const order = JSON.parse('{ "id": 7, "paid": false }');const paid = order.paid;// ^?JSON.parse gives back any, so order is any, and reading order.paid gives any too. The query on line 3 shows const paid: any, even though the value is false.
A saved size, copied and then used. Which line will TypeScript mark?
const saved: unknown = JSON.parse("16");
const size = saved;
console.log(size + 2);const saved: unknown = JSON.parse("16");const size = saved;console.log(size + 2); // line 3: 'size' is of type 'unknown'Line 2 only copies the value, so size is unknown as well. Line 3 adds 2 to it, and TypeScript says 'size' is of type 'unknown'.
TypeScript marks line 2. Add a check so the error goes away. What does the fixed code print?
const saved: unknown = JSON.parse("16");console.log(saved.toFixed(1));const saved: unknown = JSON.parse("16");if (typeof saved === "number") { console.log(saved.toFixed(1));}Inside if (typeof saved === "number"), saved is a number, so toFixed(1) is allowed. The saved value is 16, so it prints 16.0.
Line 1 has two errors. Give both parameters a real type, not any. What does the fixed code print?
function lineTotal(price, quantity) { return price * quantity;} console.log(lineTotal(3, 2));function lineTotal(price: number, quantity: number) { return price * quantity;} console.log(lineTotal(3, 2));With : number on both parameters, nothing is implicitly any, and TypeScript checks every call. lineTotal(3, 2) prints 6.
TypeScript is happy with this order. What does it print? Then give the reply a real type and fix the bug it finds.
const order: any = JSON.parse('{ "item": "Tea", "quantity": 2 }');console.log(order.quantaty * 3);type Order = { item: string; quantity: number }; const order: Order = JSON.parse('{ "item": "Tea", "quantity": 2 }');console.log(order.quantity * 3);The starter prints NaN: order.quantaty is undefined, and any hid the typo. With the Order type, TypeScript says Did you mean 'quantity'?. With the name fixed, it prints 6.
Line 2 gets an error. What type did TypeScript give cart.items? Then fix the code so the cart can hold text.
const cart = { items: [] };cart.items.push("Tea");console.log(cart.items.length);const cart: { items: string[] } = { items: [] };cart.items.push("Tea");console.log(cart.items.length);TypeScript saw only an empty list, so items became never[], a list that can hold nothing. Saying items: string[] lets line 2 add "Tea", and it prints 1.
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.
Question 1 of 7Which type turns TypeScript's checking off for a value?
Choose an answer to see the explanation.
Question 2 of 7What does TypeScript say about line 2 of this saved number?
Read the code, then predictconst data: unknown = JSON.parse("5"); console.log(data * 2);Choose an answer to see the explanation.
Question 3 of 7What does this typo on an any value print, and does TypeScript complain?
Read the code, then predictconst user: any = { name: "Asha" }; console.log(user.nmae);Choose an answer to see the explanation.
Question 4 of 7What return type does TypeScript work out for this function declaration that always throws?
Read the code, then predictfunction fail(message: string) { throw new Error(message); }Choose an answer to see the explanation.
Question 5 of 7Which value can you store in a variable of type never?
Choose an answer to see the explanation.
Question 6 of 7With strict on, what does TypeScript report for this function?
Read the code, then predictfunction double(amount) { return amount * 2; }Choose an answer to see the explanation.
Question 7 of 7What does this saved-value check print?
Read the code, then predictconst 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
anyswitches checking off: any property, any call, stored anywhere, with no errors.anyspreads: everything read from it isanytoo.JSON.parseandresponse.json()give it back.unknownaccepts every value, but you must check it, for example withtypeof, before you use it.- The
errorin acatchblock isunknown; check it withinstanceof Error. neverhas no values: a function that always throws, what's left after every check, an empty list inside an object with no type.noImplicitAny, part ofstrict, reports ananyyou 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.