cf.completefrontend
Open lab My progress
THE TYPESCRIPT FIELD GUIDE

Strict mode options

Learn the nine checks strict turns on, the extra flags like noUncheckedIndexedAccess worth adding, and how to raise strictness in a project one step at a time.

By the end, you can
  • 01
    Name what strict turns onList the checks in the strict group, say what each one reports, and turn one off on its own.
  • 02
    Add the extra checksUse noUncheckedIndexedAccess, exactOptionalPropertyTypes, and noImplicitOverride, and fix the errors they find.
  • 03
    Raise strictness safelyTurn on checks one at a time in an existing project, fixing each list of errors before the next.

One setting, many checks

A shop page looks up a product's price by name. The code below has three problems. With strict checks on, which is how this page checks every example unless it says otherwise, TypeScript marks all three, on purpose here:

A price lookup with three problems (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Product = { name: string; price: number };const products: Product[] = [{ name: "Tea", price: 3 }]; function priceOf(name) {  const product = products.find((item) => item.name === name);  return product.price;} try {  console.log(priceOf("Cake"));} catch (error) {  console.log(error.message);}

Line 1 says what a Product is, and line 2 makes a list with only tea in it. Line 4 starts priceOf, line 5 looks for the product with find, and line 6 returns its price. Line 10 asks for cake, which isn't there, so line 6 throws a TypeError. The catch block on lines 11 and 12 catches it (the try...catch lesson covers this) and prints Cannot read properties of undefined (reading 'price'). The three marks:

  • Line 4, Parameter 'name' implicitly has an 'any' type: nothing says what name holds.
  • Line 6, 'product' is possibly 'undefined': find gives back undefined when nothing matches. This is the crash.
  • Line 12, 'error' is of type 'unknown': code can throw anything, not only errors, so TypeScript won't let you read message before you check.

In Setting up a project you turned on "strict": true and saw it catch the first two. What you didn't see is that strict isn't one check. Each of these three errors comes from a different check, and each check has its own name, called a flag: a setting in compilerOptions that is true or false. strict is a shortcut that turns on nine of them. Other flags aren't in the group, and a project adds them by name:

A shop's tsconfig.json: strict plus three extra checksJSON
{  "compilerOptions": {    "strict": true,    "noUncheckedIndexedAccess": true,    "exactOptionalPropertyTypes": true,    "noImplicitOverride": true  }}

Line 3 turns on the strict group. Lines 4 to 6 add three checks from outside it, which this lesson covers one by one. In TypeScript 5.9, every flag here is false unless something turns it on. (TypeScript 6.0 and later turn strict on by default, as Setting up a project mentioned, but writing it keeps every version the same.)

Left: a window titled strict holds noImplicitAny, strictNullChecks, useUnknownInCatchVariables, and 6 more checks, all turned on by "strict": true. Right: a window titled extras holds noUncheckedIndexedAccess, exactOptionalPropertyTypes, noImplicitOverride, and more, which you add yourself."strict": truestrictnoImplicitAnystrictNullChecksuseUnknownInCatchVariables+ 6 more checksturned on togetheroutside the groupextrasnoUncheckedIndexedAccessexactOptionalPropertyTypesnoImplicitOverride+ more you can addyou add each one by name
"strict": true turns on the nine checks in the left window at once. The checks on the right aren't in the group, so you add each one by name.
Real-life analogyA building's fire safety package

When a new building is fitted out, the owner can tick one box for the full safety package. Alarms, sprinklers, and exit signs all arrive together. The owner can still cross out one item for now. Some useful things, like fire blankets in the kitchen, aren't in the package at all, so the owner orders them separately.

In real life: You tick "full safety package" on the form
In TypeScript: You write "strict": true
In real life: Alarms, sprinklers, and exit signs all get fitted
In TypeScript: noImplicitAny, strictNullChecks, and seven more turn on
In real life: You cross out the sprinklers for one room you'll finish later
In TypeScript: You add "strictNullChecks": false after strict
In real life: Fire blankets aren't in the package; you order them by name
In TypeScript: You add noUncheckedIndexedAccess by name
The main idea

strict is one switch for a group of checks. Each check is a flag with its own name, and a flag you write yourself wins over the group. Some of the most useful checks, like noUncheckedIndexedAccess, aren't in the group, so you add them one by one.

What strict turns on

Here is the price lookup again. The code never changes; only the settings do. Turn on one check at a time and watch which line each one marks:

Playground: turn on one strict check at a time
A price lookup with three problemsPop out in the code editor (opens in a new tab)TypeScript
type Product = { name: string; price: number };const products: Product[] = [{ name: "Tea", price: 3 }]; function priceOf(name) {  const product = products.find((item) => item.name === name);  return product.price;} try {  console.log(priceOf("Cake"));} catch (error) {  console.log(error.message);}
TypeScript saysNo errors
tsconfig.jsonJSON
{
  "compilerOptions": {
    "strict": false
  }
}
PrintsCannot read properties of undefined (reading 'price')
Try it yourself

No errors at all. name on line 4 is quietly any, find is trusted to always find something, and error on line 11 is any too. The code runs and prints the message from line 12, because there is no cake.

The code is checked by the real TypeScript compiler with the settings you pick, which the list under the code shows. The code never changes, so it prints the same line every time. Reset goes back to strict: false.

Start with strict: false: no errors, even though the code crashes. Each of the next three buttons turns on one flag and gets exactly one error. strict: true gets all three. Notice that line 12's message changes. With only useUnknownInCatchVariables on, it's Property 'message' does not exist on type 'unknown'; with strictNullChecks on as well, TypeScript says it more directly. The last button keeps strict on and turns one flag off. The order in the file doesn't matter: a flag you name always wins over the group.

That last button is how a project keeps strict on while it isn't ready for one or two checks yet:

tsconfig.json: strict, with two checks turned off for nowJSON
{  "compilerOptions": {    "strict": true,    "strictNullChecks": false,    "strictPropertyInitialization": false  }}

Line 3 turns on the whole group. Lines 4 and 5 turn two of its checks off again. Everything else in the group stays on, and any check a future TypeScript adds to strict turns on too. The TypeScript documentation says this plainly: a new version may add stricter checks under strict, so an upgrade can bring new errors.

In TypeScript 5.9 the group has nine flags. Read each row of the table as: this flag, what it reports, and where this lesson shows it. You've met the first two before, in any, unknown & never and null and undefined.

The nine flags strict turns on in TypeScript 5.9
The flagWhat it reportsWhere you see it
noImplicitAnyA parameter or variable with no type written, whose type TypeScript can't work out.Line 4 of the price lookup: function priceOf(name).
strictNullChecksCode that uses a value that might be null or undefined without checking first.Line 6: product.price, after a find that might find nothing.
useUnknownInCatchVariablesUsing the error in a catch block before checking what it is.Line 12: error.message.
strictPropertyInitializationA class field that a new object might not get a value for.count: number in CartBadge.
strictFunctionTypesA function that needs a narrower parameter than its caller promises to pass.logPosition stored as a ClickHandler.
strictBindCallApplyWrong arguments passed through a function's call, apply, or bind.applyDiscount.call(null, 20).
noImplicitThisthis in a function where TypeScript can't tell what this is.this.label in showLabel.
strictBuiltinIteratorReturnTreating the last result of a built-in iterator as anything, when it is really undefined.Not shown. It matters when you call next() on an iterator yourself.
alwaysStrictCode that JavaScript's strict mode doesn't allow, in every file. It also adds "use strict" to the JavaScript of files that aren't modules; modules are in strict mode already.Not shown. Every example here is a module.

Four quieter checks inside strict

noImplicitAny and strictNullChecks find the most errors. Four others find fewer, but each one catches a real kind of bug. Each example below is checked with strict on, and each has one error on purpose. First, a class field that never gets a value:

A class field nobody sets (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
class CartBadge {  count: number;  label = "Cart";} const badge = new CartBadge();console.log(`${badge.label}: ${badge.count}`);

Line 2 says every CartBadge has a count that is a number, but nothing ever gives it one. Line 3 is fine, because label starts as "Cart". strictPropertyInitialization marks line 2: Property 'count' has no initializer and is not definitely assigned in the constructor, which means "no starting value, and the constructor doesn't set one either". Line 7 prints Cart: undefined. (Classes in TypeScript covers fields.)

A click handler that needs a mouse event (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type ClickHandler = (event: Event) => void; function logPosition(event: MouseEvent) {  console.log(event.clientX);} const handler: ClickHandler = logPosition;

Line 1 describes a handler that may be given any Event, like a key press. Line 3's logPosition only works with a MouseEvent, because line 4 reads clientX, the pointer's position. Line 7 tries to store it as a ClickHandler. strictFunctionTypes marks it: Type '(event: MouseEvent) => void' is not assignable to type 'ClickHandler'. Called with a key press, line 4 would print undefined. The next lines of the message list the missing properties.

A discount called with call() (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
function applyDiscount(total: number, percent: number) {  return total - (total * percent) / 100;} console.log(applyDiscount.call(null, 20));

Every function has a call method whose first argument is the value for this and whose others go to the function (Methods and this covers it). Line 5 passes only one of the two numbers. strictBindCallApply checks those arguments like a normal call, so it says Expected 3 arguments, but got 2: the this value plus two numbers. Without it, line 5 would quietly print NaN, as it does when you run it.

this in a plain function (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
const badge = {  label: "Cart",  show: showLabel,}; function showLabel() {  return `Label: ${this.label}`;}console.log(badge.show());

Lines 1 to 4 make a badge whose show is the function on line 6. Line 7 reads this.label, but a plain function can be called on anything, so TypeScript can't know what this is. noImplicitThis marks it: 'this' implicitly has type 'any' because it does not have a type annotation. Line 9 does print Label: Cart; the check is about the calls TypeScript can't see. The fix is a this parameter, from this in TypeScript.

noUncheckedIndexedAccess: the item might not be there

Strict checks a find that might find nothing. It doesn't check an array index or a key that might not be there. You met the flag that does in Index signatures and Arrays and tuples. Here it is with the switch in your hands:

Playground: read a price that isn't there
A price list without jamPop out in the code editor (opens in a new tab)TypeScript
const prices: Record<string, number> = { tea: 3, cake: 4 }; const jam = prices["jam"];//    ^?console.log(jam.toFixed(2));
noUncheckedIndexedAccessfalse
Line 4 showsconst jam: number
TypeScriptNo errors
When it runsthrows a TypeError
Try it yourself

With the flag off, the query on line 4 says jam is a number. TypeScript reports nothing, but there is no jam in prices, so line 5 throws a TypeError when it runs.

The code is checked for real with the setting the list under it shows. Only line 5 and the setting change. Reset goes back to Flag off.

Line 1 makes a price list with tea and cake. Line 3 reads the price of jam, and the query on line 4 shows its type. With Flag off, jam is a number, and line 5 crashes with no warning. With Flag on, it's number | undefined, and line 5 is marked. With a fallback uses ?? to pick 0 when jam is undefined and prints 0.00 (Nullish coalescing & optional chaining covers ??).

A cart called prices holds two cards, tea: 3 and cake: 4. An arrow labelled prices["jam"] leads to a card that says undefined. Below it: flag off says number, flag on says number | undefined.tea: 3cake: 4pricesprices["jam"]undefinedflag off says: numberflag on says: number | undefined
prices has no jam, so prices["jam"] gives undefined. With the flag off, TypeScript still calls it a number. With the flag on, the type says what can really happen.

The flag doesn't make every array read harder. It only adds undefined where an item really might be missing:

Where the flag adds undefined, and where it doesn'tPop out in the code editor (opens in a new tab)TypeScript
const cart = ["tea", "cake"];const first = cart[0];//    ^?for (const item of cart) {  //       ^?  console.log(item.toUpperCase());}const pair: [string, number] = ["tea", 3];const product = pair[0];//    ^?
  • Line 2 reads cart[0]. The query on line 3 says string | undefined: a string[] can be empty.
  • Line 4 loops with for...of, and the query on line 5 says item is a string. A loop only visits items that are there (for...of and for...in). Line 6 prints TEA and CAKE.
  • Line 8 makes a tuple, an array with a fixed length. Line 9 reads index 0, which always exists, so the query on line 10 says string.

So code that loops, or uses methods like map, rarely notices the flag. Code that picks items by position or by key gets an error wherever it forgot that the item might be missing.

exactOptionalPropertyTypes: left out is not undefined

A property marked with ?, like theme?: "light" | "dark", is optional: an object may leave it out. In JavaScript, leaving a key out and giving it the value undefined are two different objects:

Left out, or set to undefinedPop out in the code editor (opens in a new tab)JavaScript
const defaults = { theme: "light", fontSize: 16 };
const leftOut = {};
const setToUndefined = { theme: undefined };

console.log({ ...defaults, ...leftOut }.theme);
console.log({ ...defaults, ...setToUndefined }.theme);
console.log("theme" in setToUndefined);

Line 1 holds the defaults. Line 2 leaves theme out; line 3 has a theme key holding undefined. Lines 5 and 6 spread each one over the defaults (Spread and rest covers ...). Line 5 prints light: nothing replaced the default. Line 6 prints undefined: the key was copied, value and all. Line 7 prints true, because the key exists.

Top: saved settings {} spread over the defaults give theme "light". Bottom: saved settings { theme: undefined } spread over the defaults give theme undefined.saved settingsthe theme you get{ }spread over the defaultstheme: "light"the default survives{ theme: undefined }spread over the defaultstheme: undefinedthe default is lost
Spreading { } over the defaults keeps "light". Spreading { theme: undefined } copies the undefined over it. The two look alike to an optional type, unless exactOptionalPropertyTypes is on.

By default, TypeScript treats both the same way: theme?: allows undefined too. exactOptionalPropertyTypes makes ? mean only "may be left out". Try saving the theme in different ways:

Playground: what the saved settings hold
Saved settings over the defaultsPop out in the code editor (opens in a new tab)TypeScript
type Settings = { theme: "light" | "dark"; fontSize: number };type SavedSettings = { theme?: "light" | "dark"; fontSize?: number }; const defaults: Settings = { theme: "light", fontSize: 16 };const saved: SavedSettings = { theme: undefined };const settings = { ...defaults, ...saved };//    ^?console.log(settings.theme);
Line 8 printsundefined
exactOptionalPropertyTypesfalse
TypeScriptNo errors
Try it yourself

With the flag off, theme?: also allows undefined, so line 5 is fine. The query on line 7 promises a theme of "light" | "dark", but the spread copies the undefined over the default, and line 8 prints undefined.

The code is checked for real with the setting the list under it shows, and the output is what it prints. Only line 5 and the setting change. Reset goes back to the first button.

With the flag off, line 5 is fine, and the query on line 7 says theme is "light" | "dark". Line 8 prints undefined anyway: the type made a promise the code broke. With the flag on, the same line 5 is marked, and the message ends with with 'exactOptionalPropertyTypes: true', so you know which flag is talking. Leave the theme out, or save a real one, and the error is gone. (Utility types showed the same flag with Partial.)

The flag also changes what you may write into an optional property later:

Removing a nickname the right way (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Profile = { nickname?: string; bio?: string | undefined }; const profile: Profile = { nickname: "Ash" };profile.nickname = undefined;delete profile.nickname;profile.bio = undefined;
  • Line 1 makes nickname optional, and bio optional with | undefined written out.
  • Line 4 writes undefined into nickname and is marked: Type 'undefined' is not assignable to type 'string' with 'exactOptionalPropertyTypes: true'.
  • Line 5 removes the key with delete, which is fine: now it's really left out.
  • Line 6 writes undefined into bio, which is fine, because its type says undefined is allowed.

More checks outside strict

A few more flags catch mistakes that aren't about types at all. One is about classes. When a class extends another (Class inheritance), it can replace a method of its parent by giving its own method the same name. TypeScript has a keyword to say you mean it, override, and a flag that makes it required:

Methods that replace a parent's method (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
class Widget {  draw() {    return "widget";  }} class CartWidget extends Widget {  draw() {    return "cart";  }} class BadgeWidget extends Widget {  override render() {    return "badge";  }}
  • Lines 1 to 5 make a Widget with a draw method.
  • Line 8 replaces draw in CartWidget without saying so. With noImplicitOverride on, that's marked: This member must have an 'override' modifier because it overrides a member in the base class 'Widget'.
  • Line 14 says override, but Widget has no render. Maybe it was renamed to draw last week. TypeScript marks it: This member cannot have an 'override' modifier because it is not declared in the base class 'Widget'.

Together, the two errors keep classes in step with their parents. Once every replaced method says override, renaming the parent's method gets you an error in every child that still uses the old name.

A shipping cost with a missing case (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
function shippingCost(country: string) {  if (country === "IN") {    return 0;  } else if (country === "US") {    return 5;  }} console.log(shippingCost("FR"));

Lines 2 to 6 return a cost for India and for the US. For any other country the function reaches the end without a return, so line 9 prints undefined. noImplicitReturns marks line 1: Not all code paths return a value.

TypeScript 5.9's tsc --init, the command that writes a new tsconfig.json, turns two extra flags on and lists the rest as comments for you to consider. Read each row as: this flag, what it reports, and what tsc --init does with it.

Useful checks outside the strict group
The flagWhat it reportsTurned on by tsc --init in 5.9?
noUncheckedIndexedAccessUsing an item read by index or by a key the type can't promise, without checking it exists.Yes, it's on.
exactOptionalPropertyTypesWriting undefined into an optional property that doesn't list undefined.Yes, it's on.
noImplicitOverrideA class method that replaces the parent's method without the word override.Listed, but commented out.
noImplicitReturnsA function where some paths return a value and others fall off the end.Listed, but commented out.
noFallthroughCasesInSwitchA case with code that runs on into the next case with no break.Listed, but commented out.
noPropertyAccessFromIndexSignatureReading a key that only an index signature allows with a dot, like env.apiUrl.Listed, but commented out.
noUnusedLocals, noUnusedParametersVariables and parameters that are never used.Listed, but commented out.

Turning strictness up gradually

A new project should start with strict on. An older project with strict off can have hundreds of errors waiting. Turning everything on at once gives one huge change that's hard to finish. The safer path is one check at a time: turn it on, fix its errors, merge the change, then the next. Step through it on a small user list:

Playground: raise strictness step by step
An email helper on the way to strictPop out in the code editor (opens in a new tab)TypeScript
type User = { name: string; email?: string }; const users: User[] = [  { name: "Asha", email: "asha@example.com" },  { name: "Ravi" },]; function emailDomain(user) {  return user.email.split("@")[1];} console.log(emailDomain(users[0]));
TypeScript saysNo errors
tsconfig.jsonJSON
{
  "compilerOptions": {
    "strict": false
  }
}
Printsexample.com
Step 1 of 8

The project starts with strict off. No errors, and the code prints example.com. Ravi has no email, but nothing reads his yet.

Each step is checked for real with the settings shown, and the output is what the code prints. The steps called Fix it change the code; the others change the settings. Reset goes back to step 1.

Line 1 says a User may have an email, and line 5 is a user without one. Line 8 starts emailDomain, line 9 reads the part of the email after the @, and line 12 calls it. Steps 2, 4, and 7 each turn on one check and get one error. Steps 3, 5, and 8 fix it. Step 4 found a real bug: Ravi has no email, and line 9 would crash on him. Step 6 swaps the two flags for strict: true and finds nothing new in this code.

Five cards, top to bottom, joined by arrows: strict: false, noImplicitAny, strictNullChecks, strict: true, noUncheckedIndexedAccess. Beside them: start here, 1 error to fix, 1 error to fix, no new errors, 1 error to fix.strict: falsenoImplicitAnystrictNullChecksstrict: truenoUncheckedIndexedAccessstart here1 error to fix1 error to fixno new errors1 error to fix
The steps of the playground above. Each step turns on one more check, and you fix what it finds before the next. After the two biggest checks, strict: true added nothing new in this code.

A few habits make this work in a real project:

  • Start with noImplicitAny, then strictNullChecks. They find the most errors, and the rest of the group is usually quick after them.
  • Or turn on strict and switch off the flags that aren't ready yet, as in the tsconfig.json above. Then deleting one false line is the whole next step.
  • Merge each step on its own, so nobody waits on a change with hundreds of fixes.
  • Add the extra flags last. noUncheckedIndexedAccess often finds the most errors of all in code that reads arrays by index.

Migrating JavaScript to TypeScript, later in this course, uses the same steps while moving a whole project from .js files to TypeScript.

The strict settings of a shop project

Here is a complete tsconfig.json for a shop's frontend built with a bundler like Vite. The output settings come from the next lesson; this one is about lines 6 to 11:

tsconfig.json for a shop's frontendJSON
{  "compilerOptions": {    "target": "es2022",    "module": "esnext",    "moduleResolution": "bundler",    "strict": true,    "noUncheckedIndexedAccess": true,    "exactOptionalPropertyTypes": true,    "noImplicitOverride": true,    "noImplicitReturns": true,    "noFallthroughCasesInSwitch": true,    "noEmit": true  },  "include": ["src"]}
  • Line 6 turns on the strict group.
  • Lines 7 and 8 add the two checks tsc --init also turns on: an item that might be missing, and undefined in an optional property.
  • Lines 9 to 11 add three checks that catch mistakes in classes, returns, and switch statements.
  • Line 12 says only check, don't write files, because the bundler builds the JavaScript.

Now sort the lines below by which setting reports them. Each one is checked on its own, with and without that setting.

Which setting reports it?
  • function total(price) { return price * 2; }
  • const sizes = ["S", "M"]; const big = sizes.find((size) => size === "L"); big.length;
  • const sizes = ["S", "M"]; sizes[2].toLowerCase();
  • const stock: Record<string, number> = { tea: 5 }; stock.jam.toFixed(0);
  • const order: { coupon?: string } = { coupon: undefined };
  • const profile: { nickname?: string } = {}; profile.nickname = undefined;
  • class Page { load() {} } class CartPage extends Page { load() {} }
  • class Button { click() {} } class BuyButton extends Button { click() {} }
Try it yourself
0 of 8 correct

Sort each line by the setting that reports it. Each line gets an error with that setting on, and none with it off.

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

Common mix-ups

The override keyword checks something even with the flag off. Here noImplicitOverride isn't set:

override is checked even without the flag (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
class Page {  load() {    return "page";  }} class CartPage extends Page {  override reload() {    return "cart";  }}

Line 8 says override reload(), but Page only has load. TypeScript marks it anyway and even asks Did you mean 'load'?. Writing override is always checked. What the flag adds is that leaving it out is an error too.

  • "strict turns on every check TypeScript has." No. It turns on a group of nine in TypeScript 5.9. noUncheckedIndexedAccess, exactOptionalPropertyTypes, and noImplicitOverride are outside it.
  • "Writing strict: true and strictNullChecks: false is a contradiction." No. It means "the whole group, except this one". A flag you write yourself wins over the group, wherever it sits in the file.
  • "noUncheckedIndexedAccess makes every array read a chore." No. for...of loops, methods like map, and fixed tuple indexes are unchanged. Only reads that might miss get undefined.
  • "A key set to undefined is the same as a key left out." No. A spread copies the undefined, and in finds the key. exactOptionalPropertyTypes keeps the two apart.
  • "Turning on a stricter flag changes what my code does." No. The checks only change what TypeScript reports. The one exception is alwaysStrict: in a file that isn't a module, it also adds "use strict" to the JavaScript. A module is in strict mode already, so nothing changes there.
  • "Once strict is on, upgrading TypeScript can't add errors." No. New versions may add checks to the group, so an upgrade can bring new errors. Turning one off by name is the way out while you fix them.

Practice exercises

Every exercise is TypeScript and lists the settings it's checked with under the code. Press Edit & run to change the code: the errors update as you type. Try a guess first, then use the hints.

Exercise 1 · Warm-upName the check that found it

With strict on, TypeScript marks line 4 of this code. Which check in the strict group is reporting it? Then fix it without turning the check off.

Starter codePop out in the code editor (opens in a new tab)TypeScript
try {
  JSON.parse("{ broken");
} catch (error) {
  console.log(error.name);
}

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

    Exercise 2 · Warm-upRead a type with noUncheckedIndexedAccess

    What type does firstSize get? Guess first, then point at it, or press Edit & run and add a // ^? line under it.

    Starter codePop out in the code editor (opens in a new tab)TypeScript
    const sizes = ["S", "M", "L"];const firstSize = sizes[0];

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

      Exercise 3 · PracticeFix a search with no results

      A search box shows its top result in capitals. With noUncheckedIndexedAccess on, TypeScript marks line 4, and line 8 really throws. Fix line 4 without turning the flag off. What does line 8 print then?

      Starter codePop out in the code editor (opens in a new tab)TypeScript
      const results = ["Green tea", "Black tea"]; function topResult(list: string[]) {  return list[0].toUpperCase();} console.log(topResult(results));console.log(topResult([]));

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

        Exercise 4 · PracticeFix the saved settings

        The saved settings keep the font size the reader picked, but line 5 is marked. Fix it so the default theme survives. What does the fixed code print?

        Starter codePop out in the code editor (opens in a new tab)TypeScript
        type Settings = { theme: "light" | "dark"; fontSize: number };type SavedSettings = { theme?: "light" | "dark"; fontSize?: number }; const defaults: Settings = { theme: "light", fontSize: 16 };const saved: SavedSettings = { theme: undefined, fontSize: 18 };const settings = { ...defaults, ...saved };console.log(`${settings.theme} ${settings.fontSize}`);

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

          Exercise 5 · ChallengeSay what you're replacing

          This example has noImplicitOverride on. Fix line 8 so TypeScript knows the replacement is on purpose. Which word did you add?

          Starter codePop out in the code editor (opens in a new tab)TypeScript
          class Widget {  draw() {    return "widget";  }} class CartWidget extends Widget {  draw() {    return "cart (3)";  }} console.log(new CartWidget().draw());

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

            Exercise 6 · ChallengePass every strict check

            Strict marks two lines, and the starter prints 18 and then throws on "CAKE5". Fix both errors without changing the settings. An unknown code should leave the total as it is. What does the fixed code print for "CAKE5"?

            Starter codePop out in the code editor (opens in a new tab)TypeScript
            type Coupon = { code: string; percent: number };const coupons: Coupon[] = [{ code: "TEA10", percent: 10 }]; function discounted(total, code: string) {  const coupon = coupons.find((item) => item.code === code);  return total - (total * coupon.percent) / 100;} console.log(discounted(20, "TEA10"));console.log(discounted(20, "CAKE5"));

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

              Check your understanding

              Each question names the settings it assumes. The quiz shows its code without the checker, so the error list doesn't give the answers away.

              The Strict mode options quiz · 7 questionsScore: first tries count
              1. Question 1 of 7What does "strict": true in tsconfig.json do?

                Choose an answer to see the explanation.

              2. Question 2 of 7With "strict": true and "strictNullChecks": false, what happens with this code?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                const sizes = ["S", "M"];
                const big = sizes.find((size) => size === "L");
                console.log(big.length);

                Choose an answer to see the explanation.

              3. Question 3 of 7With noUncheckedIndexedAccess on, what type does second get?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                const cart = ["tea", "cake"];
                const second = cart[1];

                Choose an answer to see the explanation.

              4. Question 4 of 7With noUncheckedIndexedAccess on, does this loop get an error?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                const cart = ["tea", "cake"];
                for (const item of cart) {
                  console.log(item.toUpperCase());
                }

                Choose an answer to see the explanation.

              5. Question 5 of 7With exactOptionalPropertyTypes on, which line does TypeScript mark?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Profile = { nickname?: string };
                
                const a: Profile = {};
                const b: Profile = { nickname: "Ash" };
                const c: Profile = { nickname: undefined };

                Choose an answer to see the explanation.

              6. Question 6 of 7Which setting makes TypeScript require the word override on a method that replaces the parent's?

                Choose an answer to see the explanation.

              7. Question 7 of 7A big project has strict off and you want it on. What's a good first step?

                Choose an answer to see the explanation.

              Key takeaways

              • strict turns on a group of nine checks in TypeScript 5.9, each a flag with its own name, like noImplicitAny and strictNullChecks.
              • A flag you write yourself wins over the group: strict: true with strictNullChecks: false keeps the rest on.
              • noUncheckedIndexedAccess adds undefined to reads by index or key that might miss. Loops and tuples are unchanged.
              • exactOptionalPropertyTypes makes ? mean "may be left out", not "may be undefined".
              • noImplicitOverride, noImplicitReturns, and noFallthroughCasesInSwitch catch mistakes in classes, returns, and switch.
              • In an existing project, turn checks on one at a time and fix each list before the next.

              Remember the one-liner.
              strict is a group of named checks: turn on the group, add the extras by name, and switch off only what you're still fixing.

              Coming next: target, lib & module. It covers the other half of tsconfig.json: which JavaScript tsc writes, which built-in types it knows, and how imports are found.

              CompleteFrontend Clear concepts. Working examples.