{"id":"l-at-5-1","courseId":"advanced-typescript","lang":"en","kind":"lesson","summary":"Choose the compiler flags that make your types trustworthy, from strict and noUncheckedIndexedAccess to verbatimModuleSyntax and moduleResolution bundler, and turn them on in an existing codebase without stopping work.","html":"<p>Every type in this course is only as trustworthy as the compiler settings behind it. The same <code dir=\"ltr\">Order</code> type that catches a missing tracking number under one tsconfig waves a crash through under another. When I joined the migration I later led, the tsconfig had <code dir=\"ltr\">strict: false</code> and the team was proud of its 100% TypeScript coverage. Most of that coverage was checking very little.</p>\n<p>This lesson is about choosing flags deliberately, and about turning them on in a real codebase without a six-week freeze.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">strict: the baseline</h2>\n<p><code dir=\"ltr\">strict</code> is not one check but a family. The ones that matter most day to day:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"Scrollable table\" tabindex=\"0\"><table><thead>\n<tr>\n<th>Flag</th>\n<th>What it catches</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code dir=\"ltr\">strictNullChecks</code></td>\n<td>Using a value that might be <code dir=\"ltr\">null</code> or <code dir=\"ltr\">undefined</code></td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">noImplicitAny</code></td>\n<td>Parameters and variables silently typed <code dir=\"ltr\">any</code></td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">strictFunctionTypes</code></td>\n<td>Unsafe callback parameter types (<a href=\"/lesson/advanced-typescript/l-at-1-2\">lesson 1-2</a>)</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">useUnknownInCatchVariables</code></td>\n<td>Treating a caught value as an <code dir=\"ltr\">Error</code> without checking</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">strictPropertyInitialization</code></td>\n<td>Class fields that are never assigned</td>\n</tr>\n</tbody>\n</table></div><p>From TypeScript 6.0, <code dir=\"ltr\">strict</code> is on by default, so a new project gets it without asking. For older projects it is still the single most valuable line in the file. Everything else in this lesson assumes it.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Two flags that strict leaves out</h2>\n<p><strong><code dir=\"ltr\">noUncheckedIndexedAccess</code></strong> makes index access honest. With it, <code dir=\"ltr\">lines[0]</code> is <code dir=\"ltr\">Line | undefined</code> and <code dir=\"ltr\">STOCK[sku]</code> is <code dir=\"ltr\">number | undefined</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><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\"> first</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> cart.lines[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">];</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">first.sku;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: 'first' is possibly 'undefined'.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> available</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> STOCK</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[sku];</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: Type 'number | undefined' is not assignable to type 'number'.</span></span></code></pre></div>\n<p>“Cannot read properties of undefined” is the most common runtime error in JavaScript, and empty arrays and missing keys are its favourite sources. This flag catches them. The cost is some extra checks where you know an index is valid (inside <code dir=\"ltr\">for (let i = 0; i &lt; xs.length; i++)</code>); <code dir=\"ltr\">for…of</code> loops and array methods like <code dir=\"ltr\">map</code> are unaffected.</p>\n<p><strong><code dir=\"ltr\">exactOptionalPropertyTypes</code></strong> separates “missing” from “present but <code dir=\"ltr\">undefined</code>”. Cartwheel’s PATCH endpoint treats <code dir=\"ltr\">{ couponCode: undefined }</code> as “remove the coupon” and <code dir=\"ltr\">{}</code> as “leave it alone”. Without the flag, both have the same type. With it:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> OrderPatch</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">couponCode</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</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\"> patch</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> OrderPatch</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { couponCode: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">undefined</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: Type '{ couponCode: undefined; }' is not assignable to type 'OrderPatch'</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// with 'exactOptionalPropertyTypes: true'. …</span></span></code></pre></div>\n<p>If you really mean “may be explicitly undefined”, say so: <code dir=\"ltr\">couponCode?: string | undefined</code>. This flag is noisier to adopt, because many libraries’ types were written without it, so turn it on after the others.</p>\n<p>TypeScript 5.9’s <code dir=\"ltr\">tsc --init</code> now enables both flags by default in the tsconfig it generates, a strong hint about where the ecosystem is heading.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 230\" role=\"img\" aria-labelledby=\"l-at-5-1-t1\"><defs><marker id=\"arrow-l-at-5-1-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-at-5-1-t1\">Three stacked layers. The bottom layer, strict, is the baseline of null and any checks. The middle layer adds noUncheckedIndexedAccess, exactOptionalPropertyTypes and noImplicitOverride for stricter correctness. The top layer covers module behaviour: verbatimModuleSyntax, moduleResolution bundler and erasableSyntaxOnly.</title>\n  <rect class=\"d-box-primary\" x=\"60\" y=\"20\" width=\"580\" height=\"56\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"350\" y=\"44\" text-anchor=\"middle\">How modules are emitted and resolved</text>\n  <text class=\"d-code\" x=\"350\" y=\"66\" text-anchor=\"middle\">verbatimModuleSyntax · moduleResolution: bundler · erasableSyntaxOnly</text>\n  <rect class=\"d-box-accent\" x=\"60\" y=\"88\" width=\"580\" height=\"56\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"350\" y=\"112\" text-anchor=\"middle\">Correctness beyond strict</text>\n  <text class=\"d-code\" x=\"350\" y=\"134\" text-anchor=\"middle\">noUncheckedIndexedAccess · exactOptionalPropertyTypes · noImplicitOverride</text>\n  <rect class=\"d-box-success\" x=\"60\" y=\"156\" width=\"580\" height=\"56\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"350\" y=\"180\" text-anchor=\"middle\">Baseline</text>\n  <text class=\"d-code\" x=\"350\" y=\"202\" text-anchor=\"middle\">strict (default from TypeScript 6.0)</text>\n</svg><figcaption>Three layers of a modern tsconfig</figcaption></figure>\n<h2 id=\"sec-3\" tabindex=\"-1\">Module flags: say what you mean</h2>\n<p><strong><code dir=\"ltr\">verbatimModuleSyntax</code></strong> (TypeScript 5.0) makes emitted imports match what you wrote. Any import without a <code dir=\"ltr\">type</code> modifier is kept; imports marked <code dir=\"ltr\">type</code> are erased. That matters because tools such as Vite, esbuild and Node’s built-in type stripping compile one file at a time and cannot look up whether <code dir=\"ltr\">Order</code> is a type:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><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\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { Order } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> './orders'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: 'Order' is a type and must be imported using a type-only import</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// when 'verbatimModuleSyntax' is enabled.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Order, placeOrder } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> './orders'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// fine</span></span></code></pre></div>\n<p><strong><code dir=\"ltr\">moduleResolution</code></strong> tells TypeScript how your imports are found. Match it to the tool that actually resolves them. For an app built by Vite or another bundler, use <code dir=\"ltr\">&quot;bundler&quot;</code> (with <code dir=\"ltr\">&quot;module&quot;: &quot;esnext&quot;</code> or <code dir=\"ltr\">&quot;preserve&quot;</code>): it understands package <code dir=\"ltr\">exports</code> and does not require file extensions. For code that Node runs directly, such as a server or a CLI, use <code dir=\"ltr\">&quot;module&quot;: &quot;nodenext&quot;</code>, which follows Node’s real rules, including <code dir=\"ltr\">.js</code> extensions in relative imports. The old <code dir=\"ltr\">&quot;node&quot;</code> setting (also called <code dir=\"ltr\">node10</code>) predates package <code dir=\"ltr\">exports</code>; it is deprecated in TypeScript 6.0 and removed in 7.0, along with <code dir=\"ltr\">baseUrl</code> and <code dir=\"ltr\">target: &quot;es5&quot;</code>.</p>\n<p><strong><code dir=\"ltr\">erasableSyntaxOnly</code></strong> (TypeScript 5.8) rejects the few TypeScript features that generate runtime code: <code dir=\"ltr\">enum</code>, namespaces with values, constructor parameter properties, and the CommonJS-style <code dir=\"ltr\">import x = require(…)</code> and <code dir=\"ltr\">export =</code>. Turn it on if your code runs through Node’s type stripping or you want TypeScript to stay “JavaScript plus types”.</p>\n<p>Put together, a tsconfig for a bundled app in 2026 looks like this:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"json\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">tsconfig.json</span><span class=\"code-actions\"><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:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"compilerOptions\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"target\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"es2022\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"module\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"esnext\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"moduleResolution\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"bundler\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"verbatimModuleSyntax\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"erasableSyntaxOnly\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"noEmit\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"strict\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"noUncheckedIndexedAccess\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"exactOptionalPropertyTypes\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"noImplicitOverride\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"skipLibCheck\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"jsx\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"react-jsx\"</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  },</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"include\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"src\"</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>\n<p><code dir=\"ltr\">noEmit</code> because the bundler produces the JavaScript and <code dir=\"ltr\">tsc</code> only checks. <code dir=\"ltr\">skipLibCheck</code> because checking every <code dir=\"ltr\">.d.ts</code> in <code dir=\"ltr\">node_modules</code> costs time and finds problems you cannot fix.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">Turning flags on in an existing codebase</h2>\n<p>Flip a flag on a big codebase and you might get 2,000 errors. Do not fix them all in one pull request. Measure first (<code dir=\"ltr\">tsc --noEmit | grep -c &quot;error TS&quot;</code>), then pick one of two approaches. Either fix directory by directory using a separate tsconfig that extends the main one and includes only the clean folders, or turn the flag on everywhere and mark the existing errors with <code dir=\"ltr\">// @ts-expect-error</code> plus a ticket reference, so new code is held to the new standard immediately and the old count only goes down.</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>Turning strictness off to unblock a release</span></p>\n<p>Setting <code dir=\"ltr\">strict: false</code> “temporarily” to get a build out is how codebases end up with years of unchecked code: nothing new gets checked while it is off, and the error count when you turn it back on only grows. Suppress individual errors with <code dir=\"ltr\">@ts-expect-error</code> and a reason instead; each one stays visible, searchable and removable.</p>\n</aside>\n<p>The exercise is a scenario: three real bugs, and you pick the flags that would have caught them. The playground itself runs with <code dir=\"ltr\">strict</code> and the ES2022 library, which is why some later features need a small declaration to work there, as you will see next.</p>\n","toc":[{"id":"sec-1","text":"strict: the baseline","level":2},{"id":"sec-2","text":"Two flags that strict leaves out","level":2},{"id":"sec-3","text":"Module flags: say what you mean","level":2},{"id":"sec-4","text":"Turning flags on in an existing codebase","level":2}],"takeaways":["<code dir=\"ltr\">strict</code> is a family of checks (null checks, implicit any, function parameter checks and more), and it is the default from TypeScript 6.0 on.","<code dir=\"ltr\">noUncheckedIndexedAccess</code> adds <code dir=\"ltr\">undefined</code> to array and record lookups, which catches the most common crash that <code dir=\"ltr\">strict</code> misses.","<code dir=\"ltr\">exactOptionalPropertyTypes</code> separates a missing property from one explicitly set to <code dir=\"ltr\">undefined</code>, which matters for PATCH bodies and spreads.","<code dir=\"ltr\">verbatimModuleSyntax</code> makes the emitted imports match what you wrote, so type-only imports must say <code dir=\"ltr\">import type</code>.","Use <code dir=\"ltr\">moduleResolution: &quot;bundler&quot;</code> for apps built by Vite or another bundler, and <code dir=\"ltr\">nodenext</code> for code that Node runs directly."],"further":[{"title":"TSConfig Reference","url":"https://www.typescriptlang.org/tsconfig/"},{"title":"noUncheckedIndexedAccess","url":"https://www.typescriptlang.org/tsconfig/noUncheckedIndexedAccess.html"},{"title":"Modules - Choosing Compiler Options","url":"https://www.typescriptlang.org/docs/handbook/modules/guides/choosing-compiler-options.html"},{"title":"TypeScript 6.0 release notes","url":"https://www.typescriptlang.org/docs/handbook/release-notes/typescript-6-0.html"}],"quiz":[{"q":"<p>With <code dir=\"ltr\">strict</code> on but <code dir=\"ltr\">noUncheckedIndexedAccess</code> off, what happens here when the cart is empty?</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><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\"> first</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> cart.lines[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</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\">(first.sku);</span></span></code></pre></div>\n","options":[{"text":"It compiles, and throws “Cannot read properties of undefined” at runtime.","why":"Correct. Without the flag, <code dir=\"ltr\">lines[0]</code> is typed as <code dir=\"ltr\">Line</code>, not <code dir=\"ltr\">Line | undefined</code>, so the empty case is invisible."},{"text":"It fails to compile, because <code dir=\"ltr\">strictNullChecks</code> knows arrays can be empty.","why":"<code dir=\"ltr\">strictNullChecks</code> only tracks <code dir=\"ltr\">null</code> and <code dir=\"ltr\">undefined</code> in declared types. Index access is optimistic unless <code dir=\"ltr\">noUncheckedIndexedAccess</code> is on."},{"text":"It compiles and logs <code dir=\"ltr\">undefined</code>.","why":"Reading <code dir=\"ltr\">.sku</code> on <code dir=\"ltr\">undefined</code> throws; it does not return <code dir=\"ltr\">undefined</code>."},{"text":"It fails to compile, because <code dir=\"ltr\">strict</code> forbids index access.","why":"Index access is always allowed; the flag only changes the resulting type."}],"answer":0},{"q":"<p><code dir=\"ltr\">type Patch = { couponCode?: string }</code>. With <code dir=\"ltr\">exactOptionalPropertyTypes</code> on, which value is rejected?</p>","options":[{"text":"<code dir=\"ltr\">{}</code>","why":"Leaving an optional property out is always allowed."},{"text":"<code dir=\"ltr\">{ couponCode: 'LOYAL5' }</code>","why":"A string is exactly what the property allows."},{"text":"<code dir=\"ltr\">{ couponCode: undefined }</code>","why":"Correct. The flag treats “present with value undefined” as different from “missing”; declare <code dir=\"ltr\">couponCode?: string | undefined</code> if you mean to allow it."}],"answer":2},{"q":"<p>Your file has <code dir=\"ltr\">import { Order, placeOrder } from './orders'</code> where <code dir=\"ltr\">Order</code> is a type. With <code dir=\"ltr\">verbatimModuleSyntax</code> on, what do you write?</p>","options":[{"text":"Nothing changes; the compiler removes <code dir=\"ltr\">Order</code> from the output automatically.","why":"That automatic elision is exactly what the flag turns off, so emitted imports match the source."},{"text":"<code dir=\"ltr\">import { type Order, placeOrder } from './orders'</code>","why":"Correct. The <code dir=\"ltr\">type</code> modifier marks <code dir=\"ltr\">Order</code> as type-only, so it is erased while <code dir=\"ltr\">placeOrder</code> stays."},{"text":"<code dir=\"ltr\">import * as orders from './orders'</code> and use <code dir=\"ltr\">orders.Order</code>","why":"A namespace import works for values, but it does not solve the type-only marking the flag asks for."},{"text":"Turn on <code dir=\"ltr\">esModuleInterop</code>.","why":"<code dir=\"ltr\">esModuleInterop</code> is about CommonJS default imports and is unrelated to type-only imports."}],"answer":1},{"q":"<p>Your Vite app has <code dir=\"ltr\">&quot;moduleResolution&quot;: &quot;node&quot;</code> in its tsconfig. What should you change it to, and why?</p>","options":[{"text":"<code dir=\"ltr\">&quot;classic&quot;</code>, because it is the most permissive.","why":"<code dir=\"ltr\">classic</code> predates npm conventions, and like <code dir=\"ltr\">node</code> it is deprecated in TypeScript 6.0 and removed in 7.0."},{"text":"<code dir=\"ltr\">&quot;nodenext&quot;</code>, because Vite runs on Node.","why":"Vite runs on Node, but your app code is resolved by the bundler, and <code dir=\"ltr\">nodenext</code> would demand file extensions the bundler does not need."},{"text":"<code dir=\"ltr\">&quot;bundler&quot;</code>, because it models how Vite resolves imports, including package <code dir=\"ltr\">exports</code>, and <code dir=\"ltr\">node</code> is deprecated.","why":"Correct. <code dir=\"ltr\">bundler</code> matches the tool that actually resolves your imports, and <code dir=\"ltr\">node</code> (<code dir=\"ltr\">node10</code>) is removed in TypeScript 7.0."}],"answer":2}],"exercise":{"kind":"choice","multiple":true,"options":["unchecked-index","exact-optional","implicit-override","skip-lib-check","verbatim-modules","allow-js"],"correct":["unchecked-index","exact-optional","implicit-override"],"title":"Which flags would have caught these bugs?","prompt":"<p>Cartwheel’s checkout already compiles with <code dir=\"ltr\">strict: true</code>. These three bugs still reached production last quarter:</p>\n<ol>\n<li><strong>Empty cart crash.</strong> The order summary read <code dir=\"ltr\">cart.lines[0].sku</code> to show a thumbnail. For an empty cart, the page crashed with <em>Cannot read properties of undefined</em>.</li>\n<li><strong>Coupons vanishing.</strong> A form sent <code dir=\"ltr\">{ couponCode: formValue.coupon }</code> to the PATCH endpoint. <code dir=\"ltr\">formValue.coupon</code> is typed <code dir=\"ltr\">string | undefined</code> (the field is optional); when it was empty, the value was <code dir=\"ltr\">undefined</code>, and the API treats a present-but-undefined <code dir=\"ltr\">couponCode</code> as “remove the coupon”, so customers lost coupons they had applied earlier.</li>\n<li><strong>Silent tax bug.</strong> <code dir=\"ltr\">PriceCalculator.applyTax()</code> was renamed to <code dir=\"ltr\">addTax()</code> in the base class. The subclass <code dir=\"ltr\">GulfPriceCalculator</code> still defined <code dir=\"ltr\">applyTax()</code>, which now overrode nothing, so Gulf orders were taxed with the default rate.</li>\n</ol>\n<p>Select <strong>every</strong> compiler flag that, added to <code dir=\"ltr\">strict</code>, would have turned at least one of these bugs into a compile error.</p>\n","hints":["Think about what type <code dir=\"ltr\">cart.lines[0]</code> has today, and which flag changes it.","One flag distinguishes <code dir=\"ltr\">{}</code> from <code dir=\"ltr\">{ couponCode: undefined }</code> for an optional property.","One flag requires the <code dir=\"ltr\">override</code> keyword on every method that overrides a base-class method, so a stale override stops compiling."],"explanation":"<p><code dir=\"ltr\">noUncheckedIndexedAccess</code> types <code dir=\"ltr\">cart.lines[0]</code> as <code dir=\"ltr\">Line | undefined</code>, forcing a check before <code dir=\"ltr\">.sku</code> (bug 1). <code dir=\"ltr\">exactOptionalPropertyTypes</code> rejects assigning <code dir=\"ltr\">undefined</code> to <code dir=\"ltr\">couponCode?: string</code>, so the form would have had to omit the key or declare <code dir=\"ltr\">string | undefined</code> explicitly (bug 2). <code dir=\"ltr\">noImplicitOverride</code> requires <code dir=\"ltr\">override applyTax()</code> in the subclass; once the base method is renamed, <code dir=\"ltr\">override</code> on a method that overrides nothing is an error (bug 3). <code dir=\"ltr\">skipLibCheck</code> reduces checking, <code dir=\"ltr\">verbatimModuleSyntax</code> is about how imports are emitted, and <code dir=\"ltr\">allowJs</code> lets JavaScript files into the program; none of them catches these bugs.</p>\n","checkLabels":{},"optionText":{"unchecked-index":{"text":"<code dir=\"ltr\">noUncheckedIndexedAccess</code>","why":"Correct. It makes <code dir=\"ltr\">cart.lines[0]</code> possibly <code dir=\"ltr\">undefined</code>, so reading <code dir=\"ltr\">.sku</code> without a check is an error (bug 1)."},"exact-optional":{"text":"<code dir=\"ltr\">exactOptionalPropertyTypes</code>","why":"Correct. It forbids writing <code dir=\"ltr\">undefined</code> into <code dir=\"ltr\">couponCode?: string</code>, which is exactly the value that removed coupons (bug 2)."},"implicit-override":{"text":"<code dir=\"ltr\">noImplicitOverride</code>","why":"Correct. With it, the subclass must mark <code dir=\"ltr\">override applyTax()</code>, and that marker fails once the base method is renamed (bug 3)."},"skip-lib-check":{"text":"<code dir=\"ltr\">skipLibCheck</code>","why":"It skips type-checking declaration files, mostly in <code dir=\"ltr\">node_modules</code>. It makes checking faster and looser, never stricter."},"verbatim-modules":{"text":"<code dir=\"ltr\">verbatimModuleSyntax</code>","why":"It controls which imports are kept in the emitted JavaScript. It is valuable for bundlers, but it does not check property access or overrides."},"allow-js":{"text":"<code dir=\"ltr\">allowJs</code>","why":"It lets <code dir=\"ltr\">.js</code> files join the program. It is a migration tool and adds no checks to existing TypeScript code."}}},"words":815,"readMinutes":4}