The problem with a missing key
A missing key often means "none yet": a shop with no plums has 0 plums. But a plain read does not give 0. It gives undefined, and doing arithmetic with it gives NaN, "not a number":
const stock: { [key: string]: number } = {"apples": 5, "pears": 2};
console.log(stock["plums"], stock["plums"] + 1);
undefined NaN
The type of stock says every value is a number, but types are erased when the code is compiled, so a missing key still reads as undefined when the program runs. The compiler does not warn you.
Start with the default, then override
The lesson's pattern has two parts. First set a variable to the default. Then use an if with in to replace it with the real value, but only when the key is there:
let plums: number = 0;
if ("plums" in stock) {
plums = stock["plums"];
}
If "plums" is a key, plums takes its value. If not, it keeps the 0. Reading this way never adds the key: Object.keys(stock).length is still 2 afterwards. With "apples" in place of "plums", the same code gives 5.
The exercise, step by step
The starter code reads and prints the pears. The task is to print the plums the same way, even though stock has none.
- Declare the default.
let plums: number = 0; - Override when the key exists. An
ifwith"plums" in stockand, inside,plums = stock["plums"]. - Print. Copy the pears line with
plumsin place ofpears.
const stock: { [key: string]: number } = {"apples": 5, "pears": 2};
let pears: number = stock["pears"];
console.log("We have " + pears + " pears");
let plums: number = 0;
if ("plums" in stock) {
plums = stock["plums"];
}
console.log("We have " + plums + " plums");
We have 2 pears
We have 0 plums
Where this pattern leads
Reading with a default is the first half of updating a count. The second half is to store the result back. Here is a variable fruit holding a name that is not in stock yet:
const fruit = "plums";
let have: number = 0;
if (fruit in stock) { have = stock[fruit]; }
stock[fruit] = have + 1;
console.log(stock);
{ apples: 5, pears: 2, plums: 1 }
The first time, the key is missing, so have stays 0 and the entry is created with 1. Run it again and the key exists, so the count goes to 2. Reading never added the key; the assignment on the last line did.
Another way: ??
The nullish coalescing operator ?? gives its right side when the left side is undefined or null. It can replace the whole pattern in one line:
console.log(stock["plums"] ?? 0, stock["apples"] ?? 0);
0 5
Do not use || for this. It replaces every falsy value, including a real 0. With {"plums": 0}, z["plums"] || 99 gives 99, while z["plums"] ?? 99 correctly gives 0. The lesson uses in so that the check is visible and explicit.
Traps
- Skipping the check.
let plums: number = stock["plums"];compiles and then holdsundefined, so the message printsWe have undefined plums. - Declaring the default after the
if. It would overwrite the value read from the dictionary. The default goes first. - Expecting the read to add the key. Reading never changes the object; only an assignment does.
Choose the default to match the meaning of a missing key. For counts it is 0, for text it might be an empty string, and for a list it might be an empty array. The type annotation on the variable, such as number, documents which default fits.
Key takeaways
- A missing key reads as
undefined, not 0. - Set the default first, then override it inside an
if (key in obj). obj[key] ?? defaultis the short form.
