{"id":"l-js-3-4","courseId":"javascript-fundamentals","lang":"en","kind":"lesson","summary":"Read possibly-missing data safely with optional chaining and nullish coalescing, and choose Map for lookups and counts and Set for unique values.","html":"<p>Real data has holes. Some expenses have a note and some do not. Some have a receipt attached. A setting the user never changed is not saved at all. Pocket must handle all of these without crashing, and without mistaking “nothing” for a real value such as zero.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">The error you will see most</h2>\n<p>Reading a property of an object that is not there is fine; you get <code dir=\"ltr\">undefined</code>. Reading a property <strong>of <code dir=\"ltr\">undefined</code></strong> is not:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"0\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> expense</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { label: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Coffee\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, amount: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">450</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(expense.receipt);          </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// undefined: fine</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">try</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(expense.receipt.url);    </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// reading .url of undefined</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">} </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">catch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (error) {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(error.message);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p><code dir=\"ltr\">TypeError: Cannot read properties of undefined (reading 'url')</code> is probably the most common error in all of JavaScript. (The <code dir=\"ltr\">try</code>/<code dir=\"ltr\">catch</code> here only lets the example keep running so you can read the message; section 5 explains it properly.) The message tells you exactly what happened: something before <code dir=\"ltr\">.url</code> was <code dir=\"ltr\">undefined</code>.</p>\n<p>Before reaching for a fix, remember the difference between the two “empty” values. <code dir=\"ltr\">undefined</code> usually means “never set”: a missing property, a parameter nobody passed, a variable without a value. <code dir=\"ltr\">null</code> is set on purpose to mean “deliberately empty”. Pocket might store <code dir=\"ltr\">receipt: null</code> for an expense where the user chose not to attach one.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Optional chaining: ?.</h2>\n<p>The <code dir=\"ltr\">?.</code> operator reads a property only if the thing on its left is not <code dir=\"ltr\">null</code> or <code dir=\"ltr\">undefined</code>. If it is, the whole expression stops and gives <code dir=\"ltr\">undefined</code> instead of throwing:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"1\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> withReceipt</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { label: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Train\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, receipt: { url: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"/r/88.png\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } };</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> withoutReceipt</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { label: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Coffee\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(withReceipt.receipt?.url);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(withoutReceipt.receipt?.url);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(withoutReceipt.tags?.[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]);          </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// works with brackets</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(withoutReceipt.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">format</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">?.());         </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// and with calls</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>Put <code dir=\"ltr\">?.</code> right after the part that may be missing. In <code dir=\"ltr\">expense.receipt?.url</code>, you are saying “<code dir=\"ltr\">expense</code> must exist, <code dir=\"ltr\">receipt</code> might not”.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Sprinkling ?. everywhere</span></p>\n<p><code dir=\"ltr\">a?.b?.c?.d</code> “fixes” every crash, and that is the problem. If <code dir=\"ltr\">expense</code> itself should always exist and suddenly does not, you want a loud error pointing at the real bug, not a quiet <code dir=\"ltr\">undefined</code> that shows up three screens later as a blank label. Use <code dir=\"ltr\">?.</code> only for data that is optional by design.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">Defaults with ??</h2>\n<p><code dir=\"ltr\">?.</code> gives you <code dir=\"ltr\">undefined</code>, and usually you want a fallback value. You met <code dir=\"ltr\">||</code> for defaults in section 2, along with its flaw: it falls back for every falsy value, including <code dir=\"ltr\">0</code> and <code dir=\"ltr\">&quot;&quot;</code>. The <strong>nullish coalescing</strong> operator <code dir=\"ltr\">??</code> falls back only for <code dir=\"ltr\">null</code> and <code dir=\"ltr\">undefined</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"2\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> settings</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { dailyLimit: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, nickname: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(settings.dailyLimit </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">||</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 5000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// 5000: the user's 0 is lost</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(settings.dailyLimit </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 5000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// 0: kept</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(settings.nickname </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"friend\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// \"\": kept</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(settings.currency </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"USD\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);    </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// missing: default used</span></span></code></pre><div class=\"run-output\" data-run-output=\"2\" hidden></div></div>\n<p>Use <code dir=\"ltr\">??</code> for defaults. Use <code dir=\"ltr\">||</code> only when you really do want empty strings and zeros replaced too. They combine naturally with <code dir=\"ltr\">?.</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"3\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> expense</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { label: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Coffee\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> receiptUrl</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> expense.receipt?.url </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"No receipt\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(receiptUrl);</span></span></code></pre><div class=\"run-output\" data-run-output=\"3\" hidden></div></div>\n<p>There is also an assignment form, <code dir=\"ltr\">settings.currency ??= &quot;USD&quot;</code>, which sets the property only if it is currently <code dir=\"ltr\">null</code> or <code dir=\"ltr\">undefined</code>.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">Map: lookups with any key</h2>\n<p>In the last lesson you used a plain object to total categories. Objects work well for records with known property names. For a collection that grows with data, keyed by values you do not know in advance, JavaScript has a dedicated structure, <strong>Map</strong>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"4\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> counts</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> category</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> of</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"travel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"fun\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]) {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  counts.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">set</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(category, (counts.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">get</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(category) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(counts.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">get</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), counts.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">has</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"rent\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), counts.size);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">category</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">count</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">of</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> counts) {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">category</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}: ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">count</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre><div class=\"run-output\" data-run-output=\"4\" hidden></div></div>\n<p><code dir=\"ltr\">set(key, value)</code> stores, <code dir=\"ltr\">get(key)</code> reads (or gives <code dir=\"ltr\">undefined</code>), <code dir=\"ltr\">has(key)</code> checks, <code dir=\"ltr\">delete(key)</code> removes and <code dir=\"ltr\">size</code> counts entries. A <code dir=\"ltr\">for...of</code> loop gives you <code dir=\"ltr\">[key, value]</code> pairs in the order they were first added.</p>\n<p>Why not always use an object? A Map accepts <strong>any value as a key</strong>, including numbers and objects, where an object turns every key into a string. It has a real <code dir=\"ltr\">size</code>. And it has no inherited properties to trip over: an object keyed by user-typed text can collide with built-in names such as <code dir=\"ltr\">constructor</code>. A good default: <strong>objects for records with fixed fields, Maps for lookup tables built from data.</strong></p>\n<h2 id=\"sec-5\" tabindex=\"-1\">Set: each value once</h2>\n<p>A <strong>Set</strong> is a collection where every value appears at most once. Adding a value that is already there does nothing:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"5\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"5\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> categories</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"travel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"fun\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"travel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">];</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> unique</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Set</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(categories);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(unique.size, unique.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">has</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"fun\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">));</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">unique.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">add</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);               </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// already there: no change</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">([</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">unique]);         </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// back to an array, in first-seen order</span></span></code></pre><div class=\"run-output\" data-run-output=\"5\" hidden></div></div>\n<p><code dir=\"ltr\">[...new Set(list)]</code> is the standard one-line way to remove duplicates from an array. <code dir=\"ltr\">has</code> is also much faster than <code dir=\"ltr\">includes</code> on a large array, because a Set does not search item by item. Recent versions of JavaScript added set operations such as <code dir=\"ltr\">union</code> and <code dir=\"ltr\">intersection</code>, available in all current browsers.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Sets compare objects by reference</span></p>\n<p>A Set (and a Map key) treats two objects as the same only if they are the same object, using the same reference rule as <code dir=\"ltr\">===</code>. Two separate <code dir=\"ltr\">{ label: &quot;Coffee&quot; }</code> objects are two different entries. Deduplicate objects by an id instead, for example with a Set of <code dir=\"ltr\">e.id</code> values.</p>\n</aside>\n<p>That completes Pocket’s data toolkit: arrays, objects, Maps and Sets, and the operators for missing values. In section 4, all of it goes onto a real web page.</p>\n","toc":[{"id":"sec-1","text":"The error you will see most","level":2},{"id":"sec-2","text":"Optional chaining: ?.","level":2},{"id":"sec-3","text":"Defaults with ??","level":2},{"id":"sec-4","text":"Map: lookups with any key","level":2},{"id":"sec-5","text":"Set: each value once","level":2}],"takeaways":["Reading a property of <code dir=\"ltr\">undefined</code> or <code dir=\"ltr\">null</code> throws a TypeError; <code dir=\"ltr\">?.</code> stops and returns <code dir=\"ltr\">undefined</code> instead.","<code dir=\"ltr\">??</code> supplies a default only for <code dir=\"ltr\">null</code> and <code dir=\"ltr\">undefined</code>, so real values like <code dir=\"ltr\">0</code> and <code dir=\"ltr\">&quot;&quot;</code> survive, unlike with <code dir=\"ltr\">||</code>.","Use <code dir=\"ltr\">?.</code> only where data is genuinely optional; spreading it everywhere hides real bugs.","A Map stores key-value pairs with any type of key, keeps insertion order, and has <code dir=\"ltr\">get</code>, <code dir=\"ltr\">set</code>, <code dir=\"ltr\">has</code> and <code dir=\"ltr\">size</code>.","A Set stores each value at most once, so <code dir=\"ltr\">[...new Set(list)]</code> removes duplicates."],"further":[{"title":"Optional chaining (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining"},{"title":"Nullish coalescing operator (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing"},{"title":"Map (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map"},{"title":"Set (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set"}],"quiz":[{"q":"<p>An expense may or may not have a <code dir=\"ltr\">receipt</code> object. Which expression reads the receipt’s <code dir=\"ltr\">url</code> without ever throwing?</p>","options":[{"text":"<code dir=\"ltr\">expense.receipt.url</code>","why":"When <code dir=\"ltr\">receipt</code> is <code dir=\"ltr\">undefined</code>, reading <code dir=\"ltr\">.url</code> from it throws “Cannot read properties of undefined”."},{"text":"<code dir=\"ltr\">expense?.receipt.url</code>","why":"The <code dir=\"ltr\">?.</code> protects against <code dir=\"ltr\">expense</code> being missing, but <code dir=\"ltr\">receipt</code> is the optional part here, and <code dir=\"ltr\">.url</code> on it still throws."},{"text":"<code dir=\"ltr\">expense.receipt?.url</code>","why":"Correct. If <code dir=\"ltr\">receipt</code> is <code dir=\"ltr\">null</code> or <code dir=\"ltr\">undefined</code>, the whole expression stops and evaluates to <code dir=\"ltr\">undefined</code>."}],"answer":2},{"q":"<p>A user’s saved tip percentage is <code dir=\"ltr\">0</code>. What does <code dir=\"ltr\">const tip = saved.tip ?? 15;</code> give?</p>","options":[{"text":"<code dir=\"ltr\">0</code>","why":"Correct. <code dir=\"ltr\">??</code> only falls back for <code dir=\"ltr\">null</code> and <code dir=\"ltr\">undefined</code>, and 0 is neither."},{"text":"<code dir=\"ltr\">15</code>","why":"That is what <code dir=\"ltr\">||</code> would give, because 0 is falsy. Avoiding exactly that is why <code dir=\"ltr\">??</code> exists."},{"text":"<code dir=\"ltr\">undefined</code>","why":"The left side has a real value, 0, so <code dir=\"ltr\">??</code> returns it."}],"answer":0},{"q":"<p>You need to count how many expenses each category has, and later list categories in the order they first appeared. Which fits best?</p>","options":[{"text":"An array of category names, using <code dir=\"ltr\">indexOf</code> to find each one.","why":"Searching an array for every expense gets slow and the counts need a second array kept in sync."},{"text":"A Set of category names.","why":"A Set records that a value exists, but it cannot store a count next to it."},{"text":"A Map from category to count.","why":"Correct. <code dir=\"ltr\">get</code>/<code dir=\"ltr\">set</code> update counts directly, and a Map remembers insertion order when you loop over it."},{"text":"A string with categories joined by commas.","why":"Strings are immutable and must be split and searched every time; they are not built for lookups."}],"answer":2},{"q":"<p>What does <code dir=\"ltr\">new Set([&quot;food&quot;, &quot;travel&quot;, &quot;food&quot;, &quot;fun&quot;]).size</code> return?</p>","options":[{"text":"<code dir=\"ltr\">4</code>","why":"A Set ignores values it already contains, so the second “food” is not added."},{"text":"<code dir=\"ltr\">3</code>","why":"Correct. The Set holds “food”, “travel” and “fun”, each once."},{"text":"<code dir=\"ltr\">2</code>","why":"Only the duplicate “food” is dropped; “travel” and “fun” each appear once and both stay."}],"answer":1}],"exercise":{"kind":"web","mode":"js","starter":{"js":"const expenses = [\n  { label: \"Coffee\", amount: 450, category: \"food\" },\n  { label: \"Train\", amount: 1220, category: \"travel\", receipt: { url: \"/r/88.png\" } },\n  { label: \"Lunch\", amount: 1350, category: \"food\", receipt: null },\n  { label: \"Gift\", amount: 5000 },\n];\n\n// The receipt's url, or \"No receipt\" (receipt may be missing or null)\nfunction receiptLabel(expense) {\n  return expense.receipt.url;\n}\n\n// settings may be undefined; a saved limit of 0 is a real choice\nfunction dailyLimit(settings) {\n  return settings.dailyLimit || 5000;\n}\n\n// A Map from category to how many expenses have it;\n// expenses without a category count as \"other\"\nfunction countByCategory(expenses) {\n}\n\n// Each category once, in the order first seen (missing ones as \"other\")\nfunction uniqueCategories(expenses) {\n  return expenses.map((e) => e.category);\n}\n\nconsole.log(receiptLabel(expenses[1]), dailyLimit({ dailyLimit: 0 }));\n"},"solution":{"js":"const expenses = [\n  { label: \"Coffee\", amount: 450, category: \"food\" },\n  { label: \"Train\", amount: 1220, category: \"travel\", receipt: { url: \"/r/88.png\" } },\n  { label: \"Lunch\", amount: 1350, category: \"food\", receipt: null },\n  { label: \"Gift\", amount: 5000 },\n];\n\nfunction receiptLabel(expense) {\n  return expense.receipt?.url ?? \"No receipt\";\n}\n\nfunction dailyLimit(settings) {\n  return settings?.dailyLimit ?? 5000;\n}\n\nfunction countByCategory(expenses) {\n  const counts = new Map();\n  for (const e of expenses) {\n    const category = e.category ?? \"other\";\n    counts.set(category, (counts.get(category) ?? 0) + 1);\n  }\n  return counts;\n}\n\nfunction uniqueCategories(expenses) {\n  return [...new Set(expenses.map((e) => e.category ?? \"other\"))];\n}\n\nconsole.log(receiptLabel(expenses[1]), dailyLimit({ dailyLimit: 0 }));\n"},"checks":[{"id":"receipt","test":"return receiptLabel(expenses[1]) === '/r/88.png' && receiptLabel(expenses[0]) === 'No receipt' && receiptLabel(expenses[2]) === 'No receipt';\n"},{"id":"limit","test":"return dailyLimit({ dailyLimit: 0 }) === 0 && dailyLimit({ dailyLimit: 2500 }) === 2500 && dailyLimit({}) === 5000 && dailyLimit(undefined) === 5000;\n"},{"id":"counts-map","test":"const m = countByCategory(expenses);\nreturn m instanceof Map && m.get('food') === 2 && m.get('travel') === 1 && m.get('other') === 1 && m.size === 3;\n"},{"id":"unique","test":"const u = uniqueCategories(expenses);\nreturn Array.isArray(u) && u.join() === 'food,travel,other';\n"}],"title":"Handle missing data safely","prompt":"<p>Some of Pocket’s data is incomplete. Fill in four functions:</p>\n<ul>\n<li><code dir=\"ltr\">receiptLabel(expense)</code> returns the receipt’s <code dir=\"ltr\">url</code>, or <code dir=\"ltr\">&quot;No receipt&quot;</code> when <code dir=\"ltr\">receipt</code> is missing or <code dir=\"ltr\">null</code>.</li>\n<li><code dir=\"ltr\">dailyLimit(settings)</code> returns <code dir=\"ltr\">settings.dailyLimit</code>, or <code dir=\"ltr\">5000</code> when there is no limit. <code dir=\"ltr\">settings</code> itself may be <code dir=\"ltr\">undefined</code>, and a saved limit of <code dir=\"ltr\">0</code> must be kept.</li>\n<li><code dir=\"ltr\">countByCategory(expenses)</code> returns a <strong>Map</strong> from category to the number of expenses in it. Expenses without a category count as <code dir=\"ltr\">&quot;other&quot;</code>.</li>\n<li><code dir=\"ltr\">uniqueCategories(expenses)</code> returns an array with each category once, in the order first seen (missing ones as <code dir=\"ltr\">&quot;other&quot;</code>).</li>\n</ul>\n","hints":["Put <code dir=\"ltr\">?.</code> right after the part that may be missing, and <code dir=\"ltr\">??</code> after that for the fallback: <code dir=\"ltr\">expense.receipt?.url ?? &quot;No receipt&quot;</code>.","<code dir=\"ltr\">||</code> loses the 0. <code dir=\"ltr\">settings?.dailyLimit ?? 5000</code> keeps it and also survives <code dir=\"ltr\">settings</code> being undefined.","Count with <code dir=\"ltr\">counts.set(category, (counts.get(category) ?? 0) + 1)</code>. For unique values, build a Set from the mapped categories and spread it back into an array."],"explanation":"<p><code dir=\"ltr\">?.</code> stops reading as soon as it meets <code dir=\"ltr\">null</code> or <code dir=\"ltr\">undefined</code> and produces <code dir=\"ltr\">undefined</code>, which <code dir=\"ltr\">??</code> then replaces with the fallback. Because <code dir=\"ltr\">??</code> only reacts to <code dir=\"ltr\">null</code> and <code dir=\"ltr\">undefined</code>, a limit of <code dir=\"ltr\">0</code> passes through untouched, which <code dir=\"ltr\">||</code> would have discarded. The Map counts each category with <code dir=\"ltr\">get</code> and <code dir=\"ltr\">set</code>, and <code dir=\"ltr\">?? 0</code> handles a category’s first appearance. <code dir=\"ltr\">new Set(...)</code> drops repeated categories while keeping first-seen order, and spreading it with <code dir=\"ltr\">[...set]</code> turns it back into an array.</p>\n","checkLabels":{"receipt":"<code dir=\"ltr\">receiptLabel</code> returns the url or “No receipt”","limit":"<code dir=\"ltr\">dailyLimit</code> keeps 0, defaults to 5000, and survives missing settings","counts-map":"<code dir=\"ltr\">countByCategory</code> returns a Map of counts, with “other” for missing categories","unique":"<code dir=\"ltr\">uniqueCategories</code> lists each category once, in first-seen order"}},"words":699,"readMinutes":3}