cf.completefrontend
Open lab My progress
THE TYPESCRIPT FIELD GUIDE

null, undefined & strictNullChecks

See how TypeScript makes you handle values that may be missing: strictNullChecks, ?. and ??, the ! assertion, and optional parameters, in code you can break.

By the end, you can
  • 01
    Read a missing-value errorExplain string | null and 'sort' is possibly 'null', and what strictNullChecks changes.
  • 02
    Pick the right toolHandle a value that may be missing with a check, ?., ??, or !, and know what each does when the value really is missing.
  • 03
    Tell optional from undefinedKnow when a parameter or property can be left out, and when it must be written even if it's undefined.

A value that might not be there

A search page reads its web address to see what the shopper asked for. The address ?q=tea asks for tea, with no sort order. Here is plain JavaScript asking for the sort order anyway:

Asking for a sort order that isn't therePop out in the code editor (opens in a new tab)JavaScript
const params = new URLSearchParams("?q=tea");
const sort = params.get("sort");
console.log(sort.toUpperCase());

Line 1 reads the part of the address after ? with URLSearchParams (the URL objects lesson covers it). Line 2 asks for sort. The address has no sort, so get gives back null, JavaScript's value for "nothing here". Line 3 calls .toUpperCase() on null, and the code stops with TypeError: Cannot read properties of null. Nothing warned you before it ran.

Here is the same code in TypeScript:

The same sort order in TypeScript (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
const params = new URLSearchParams("?q=tea");const sort = params.get("sort");//    ^?console.log(sort.toUpperCase());

The query on line 3 shows the type TypeScript gives sort: string | null. The | means "or", so sort is a string or null. TypeScript marks line 4, on purpose here, because it's the bug:

  • 'sort' is possibly 'null' means "this value might be null, and you're using it as if it can't be".
  • ts(18047) is the error's number. A value that might be undefined gets a sister error, ts(18048), which you'll meet below.
A box named sort with two cards sticking out of it: the text price, and null. Under it, its type: string | null. An arrow leads to an editor window showing sort.toUpperCase() and the note 'sort' is possibly 'null'."price"nullsortstring | nulleditorsort.toUpperCase()'sort' is possibly 'null'TypeScript asks you to check
params.get gives back text or null, so sort may hold either card. TypeScript marks line 4, because null has no toUpperCase.
Real-life analogyKeys left at the front desk

A friend says they'll leave your car keys at a hotel's front desk. When you ask, the desk may hand you the keys, or say nothing was left for you. A careful friend reminds you of the second answer, so you look in your hand before you walk out to the car park.

In real life: You ask the front desk for your keys
In TypeScript: params.get("sort")
In real life: The desk hands you the keys, or says nothing was left
In TypeScript: It gives a string, or null
In real life: A friend reminds you: they might not be there
In TypeScript: TypeScript says 'sort' is possibly 'null'
In real life: You look in your hand before you walk to the car
In TypeScript: You check sort before you call .toUpperCase()
The main idea

A value that might be missing has null or undefined in its type, like string | null. With strictNullChecks on, TypeScript won't let you use it as a string until you've handled the missing case: with a check, a backup, or ?.. When the code runs, null and undefined are the same JavaScript values as always. TypeScript only makes sure you planned for them.

strictNullChecks: null gets its own type

Whether TypeScript keeps null in a type depends on one setting, strictNullChecks. Settings like this live in a project's tsconfig.json file. Turning on strict turns it on, along with a group of other checks, and a new project made with tsc --init has strict on:

tsconfig.json with strict onJSON
{
  "compilerOptions": {
    "strict": true
  }
}

Line 3 turns on strict, and with it strictNullChecks. Every example in this lesson is checked with it on, unless it says otherwise. Switch it off and on for the overview's code:

Playground: turn strictNullChecks off and on
The sort order, checked with one settingPop out in the code editor (opens in a new tab)TypeScript
const params = new URLSearchParams("?q=tea");const sort = params.get("sort");//    ^?console.log(sort.toUpperCase());
The type of sortstring | null
What happens
TypeScript1 error: line 4
When it runsthrows a TypeError
Try it yourself

The flag is on, as strict: true sets it. The query on line 3 shows string | null: sort may be text, or it may be null. Line 4 gets 'sort' is possibly 'null', so you find the problem now.

The real TypeScript compiler checks the same four lines with each setting. Only the flag changes. Reset turns it back on.

With the flag on, the query on line 3 shows string | null, and line 4 has its error. With the flag off, the query shows only string, and the error is gone. The code throws the same TypeError either way: turning the flag off removes the warning, not the bug. TypeScript's own documentation recommends keeping it on.

With the flag on, null and undefined are types of their own. A plain string can't hold either one, so a type has to say when something may be missing:

A type has to allow null (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
let nickname: string = null;let middleName: string | null = null;let lastName: string | null = undefined;

Line 1 says nickname is a string and gives it null. TypeScript says Type 'null' is not assignable to type 'string': null isn't allowed in a plain string. Line 2 says middleName is string | null, so null is fine. Line 3 gives that same type undefined and gets Type 'undefined' is not assignable to type 'string | null'. So null and undefined are two different types: a type that allows one doesn't allow the other.

Left, strictNullChecks false: a box nickname of type string holds the text Ash and null. Right, strictNullChecks true: a box nickname of type string holds only Ash, and a box middleName of type string | null holds Lee and null.strictNullChecks: false"Ash"nullnicknamestringstrictNullChecks: true"Ash"nicknamestring"Lee"nullmiddleNamestring | null
With the flag off, null fits in any type, so nothing warns you. With it on, a plain string holds only text, and null goes only where the type says so, like string | null.

You rarely write | null yourself at first. The browser's own functions, and your own data types, already say it. Read the queries, or point at each name:

Three everyday values that may be missingTypeScript
type CartItem = { name: string; price: number };const cart: CartItem[] = [{ name: "Tea", price: 3 }]; const searchBox = document.querySelector("input");//    ^?const theme = localStorage.getItem("theme");//    ^?const cake = cart.find((item) => item.name === "Cake");//    ^?

Line 4 asks the page for an <input> element, and the query on line 5 shows HTMLInputElement | null. Line 6 reads a saved setting from the browser's storage (see Web storage), and the query on line 7 shows string | null. Line 8 searches the cart with find (see Searching arrays), and the query on line 9 shows CartItem | undefined. This example only shows types, because localStorage needs a real site, so it has no Edit & run.

The table lists where missing values come from in everyday frontend code. Read each row as: this code, the type TypeScript gives it, and when the value is missing.

Where missing values come from
The codeIts typeWhen is it missing?
document.querySelector("input")HTMLInputElement | nullIt gives null when no element on the page matches.
params.get("sort")string | nullIt gives null when the address has no sort.
localStorage.getItem("theme")string | nullIt gives null when nothing was saved under that name.
cart.find(…)CartItem | undefinedIt gives undefined when no item matches.
user.nickname, from nickname?: stringstring | undefinedIt gives undefined when the object leaves the property out.

To use a value like this, check it first:

Check for null, then use the textPop out in the code editor (opens in a new tab)TypeScript
const params = new URLSearchParams("?q=tea");const sort = params.get("sort"); if (sort !== null) {  console.log(sort.toUpperCase());  //          ^?} else {  console.log("No sort chosen");}

Line 4 checks that sort isn't null. Inside the braces, the query on line 6 shows const sort: string: TypeScript knows the check passed, so line 5 is fine. Line 8 runs when sort is null, and here it prints No sort chosen. This is the narrowing you met in Union types: a check makes the type smaller. The Narrowing lesson covers it in depth.

Reading safely with ?. and ??

JavaScript has two operators for values that may be missing (the Nullish coalescing and optional chaining lesson covers them):

  • ?. reads a property only when the value before it isn't null or undefined. Otherwise it stops and gives undefined.
  • ?? gives a backup when the value on its left is null or undefined.

TypeScript understands both and works out the types they give. Here is a user profile where the address is optional:

Reading a city from a missing address (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Profile = { name: string; address?: { city: string } }; const asha: Profile = { name: "Asha" };console.log(asha.address.city);

Line 1 describes a Profile. The ? in address?: makes the property optional: a profile may leave it out. Line 3 makes Asha's profile with no address. Line 4 reads asha.address.city, and TypeScript says 'asha.address' is possibly 'undefined'. It's right: when this runs, it throws a TypeError.

Read with ?. and give a backup with ??Pop out in the code editor (opens in a new tab)TypeScript
type Profile = { name: string; address?: { city: string } }; const asha: Profile = { name: "Asha" };const city = asha.address?.city;//    ^?const label = city ?? "No city yet";//    ^?console.log(label);

Line 4 reads with ?. instead. Asha has no address, so ?. stops and gives undefined. The query on line 5 shows const city: string | undefined: TypeScript added undefined to the type, because ?. can give it. Line 6 uses ?? to swap a missing city for "No city yet". The query on line 7 shows const label: string: the backup takes the place of undefined, so the type has none left. Line 8 prints No city yet.

A cart named asha holds only a card name: Asha, with no address. An arrow marked ?.city leads to a card undefined, labelled stops here. An arrow marked ?? leads to a card No city yet, labelled the backup.name: "Asha"ashano address?.cityundefinedstops here??"No city yet"the backup
Line 4 asks for asha.address?.city. Asha has no address, so ?. stops and gives undefined. Line 6's ?? swaps that for the backup, "No city yet".

?? isn't the same as ||. || also replaces 0, "", and false, which can be real values. TypeScript can't catch this mix-up, because both give a number here:

A font size of 0 with || and with ??Pop out in the code editor (opens in a new tab)TypeScript
type Settings = { fontSize?: number }; const settings: Settings = { fontSize: 0 };const withOr = settings.fontSize || 16;const withNullish = settings.fontSize ?? 16;//    ^?console.log(withOr, withNullish);

Line 3 sets a font size of 0. Line 4 uses ||, which treats 0 as missing and gives 16. Line 5 uses ??, which keeps 0. The query on line 6 shows const withNullish: number, and withOr is a number too, so there's no error either way. Line 7 prints 16 0. When 0 or empty text is a real value, use ??.

The non-null assertion !

Sometimes you know more than TypeScript. A search page's own HTML has a search box, so asking for it always finds one. But TypeScript can't see your HTML:

The search box might be missing (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
const searchBox = document.querySelector("input");//    ^?console.log(searchBox.value);

Line 1 asks the page for its <input>. The query on line 2 shows const searchBox: HTMLInputElement | null, because some page might have none. Line 3 reads the box's value and gets 'searchBox' is possibly 'null'. This page does have a box, with tea typed in it, so when it runs the code prints tea anyway.

A ! right after a value is the non-null assertion. It tells TypeScript: "this isn't null or undefined, trust me".

Promise that the search box is therePop out in the code editor (opens in a new tab)TypeScript
const searchBox = document.querySelector("input")!;//    ^?console.log(searchBox.value);

Line 1 ends with !. The query on line 2 shows const searchBox: HTMLInputElement: null is gone from the type. Line 3 has no error and prints tea.

The ! is only a promise. Nothing checks it when the code runs. Make the same promise about the sort order, which really is missing:

A promise that isn't truePop out in the code editor (opens in a new tab)TypeScript
const params = new URLSearchParams("?q=tea");const sort = params.get("sort")!;//    ^?console.log(sort.toUpperCase());

Line 2 ends with !, so the query on line 3 shows const sort: string, and line 4 has no error. When it runs, sort is null, and line 4 throws the same TypeError as the overview. Here is what the browser gets:

A file search.ts holds params.get("sort")! with the note no error. An arrow labelled types removed leads to a file search.js holding params.get("sort") without the !, where it ends in a TypeError.search.tsparams.get("sort")!no errorTypeScript believes youtypes removedsearch.jsparams.get("sort")TypeErrornothing checks it here
You write ! in search.ts, and TypeScript stops asking. The ! is removed with the types, so search.js reads sort with no check, and a missing sort throws a TypeError.
What the browser gets: the ! is gonePop out in the code editor (opens in a new tab)JavaScript
const params = new URLSearchParams("?q=tea");
const sort = params.get("sort");
console.log(sort.toUpperCase());

It's the overview's JavaScript, line for line. The ! was removed with the types, so it couldn't stop anything. Use ! only when you're sure, like an element your own HTML always has. When you're not sure, check the value or give a backup.

The table compares four ways to handle a value that might be missing. Read each row as: what you write, what TypeScript does, and what happens when the value really is missing.

Four ways to handle a value that might be missing
You writeWhat TypeScript doesWhen the value is missing
if (sort !== null) { … }Inside the braces, it knows sort is a string.The other branch runs. Nothing throws.
sort ?? "relevance"The result is a string: the backup takes the place of null.You get the backup, "relevance".
sort?.toUpperCase()The result is string | undefined.You get undefined. Nothing throws.
sort!.toUpperCase()It takes your word that sort isn't null, and reports nothing.It throws a TypeError, as if you had written no check at all.

Optional parameters vs undefined

A function's parameter can be missing too, when a call leaves it out (see Parameters). A ? after a parameter's name makes it optional. That's different from a parameter whose type includes undefined. Swap line 1 between four ways of writing the name parameter, and watch lines 8 and 9:

Playground: four ways to write the name parameter
A greeting with a name that may be left outPop out in the code editor (opens in a new tab)TypeScript
function greet(name?: string) {  const shown = name ?? "guest";  //            ^?  return `Hello, ${shown}`;} console.log(greet("Asha"));console.log(greet());console.log(greet(undefined));
Console outputHello, Asha / Hello, guest / Hello, guest
What happens
name insidestring | undefined
TypeScriptNo errors
Try it yourself

name?: string makes the parameter optional: a call may leave it out. Inside, the query on line 3 shows string | undefined, because a left-out argument is undefined. All three calls are fine.

TypeScript checks the code for real each time. Each button changes only line 1. Reset goes back to name?: string.

Line 7 passes "Asha", line 8 passes nothing, and line 9 passes undefined. With name?: string, all three are fine, and the query on line 3 shows string | undefined. With name: string | undefined, line 8 gets Expected 1 arguments, but got 0: the name must be passed, even if it's undefined. With name: string, line 9 is marked too. With name = "guest", a default value, every call is fine again, and inside the function name is a plain string.

The console output never changes: Hello, Asha, then Hello, guest twice. When the code runs, a left-out argument is undefined either way. Only what TypeScript allows changes.

The table sums up the four. Read each row as: what you write, whether a call can leave the name out, and the type of name inside the function.

Four ways to write a parameter that may be missing
You writeCan a call leave it out?The type of name inside
name?: stringYes.string | undefined
name: string | undefinedNo. It must pass something, even undefined.string | undefined
name: stringNo, and it can't pass undefined either.string
name = "guest"Yes. A left-out name becomes "guest".string

Object properties work the same way. nickname?: may be left out of the object, but bio: string | undefined must be written, even when its value is undefined:

Optional or undefined in a profile (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Profile = { name: string; nickname?: string; bio: string | undefined }; const asha: Profile = { name: "Asha", bio: undefined };const ravi: Profile = { name: "Ravi", nickname: "Rav" };

Line 1 describes a Profile with an optional nickname and a bio that may be undefined. Line 3 leaves out the nickname and writes bio: undefined, which is fine. Line 4 writes a nickname but leaves out bio, and gets an error that starts Property 'bio' is missing. In plain words: the object has no bio, and the type says it must have one.

A search page that handles a missing value

Back to the search page. The playground has this lesson's five ways to handle sort on line 4, and two page addresses on line 1: one with no sort, and one with sort=price. Try each way with each address.

Playground: five ways to handle a missing sort
A search page reads its sort orderPop out in the code editor (opens in a new tab)TypeScript
const params = new URLSearchParams("?q=tea");const sort = params.get("sort"); console.log(sort.toUpperCase());
When it runsthrows a TypeError
What happens
Address?q=tea
TypeScript1 error: line 4
Try it yourself

Line 4 uses sort without a check, so TypeScript says 'sort' is possibly 'null'. With a sort in the address it prints PRICE. Without one, it throws.

The first row of buttons changes line 4, and the second changes the address on line 1. TypeScript checks the code for real, and the result is what it does when it runs. Reset goes back to Use it as is, with no sort.

With ?q=tea&sort=price, every way prints PRICE, even Use it as is, which still has its error. TypeScript checks every value sort could have, not just the one this address gives. With ?q=tea, Use it as is and Promise with ! both throw a TypeError. Check for null prints No sort chosen, Backup with ?? prints RELEVANCE, and Read with ?. prints undefined, which a shopper shouldn't see. The check and the ?? backup handle both addresses well.

Real data mixes all of this. A profile from a server may leave out a nickname, have no picture yet, and have no address:

A profile line with three missing valuesPop out in the code editor (opens in a new tab)TypeScript
type Profile = {  name: string;  nickname?: string;  avatarUrl: string | null;  address?: { city: string };}; function profileLine(user: Profile) {  const shown = user.nickname ?? user.name;  const city = user.address?.city ?? "somewhere";  const avatar = user.avatarUrl ?? "/default-avatar.png";  return `${shown} from ${city} (${avatar})`;} console.log(profileLine({ name: "Asha", avatarUrl: null }));console.log(profileLine({ name: "Ravi", nickname: "Rav", avatarUrl: "/ravi.png", address: { city: "Pune" } }));

Lines 1 to 6 describe the profile: nickname and address are optional, and avatarUrl is a string or null. Line 9 shows the nickname, or the name when there's no nickname. Line 10 reads the city with ?. and falls back to "somewhere". Line 11 falls back to a default picture. Line 15 prints Asha from somewhere (/default-avatar.png), and line 16 prints Rav from Pune (/ravi.png). There's no ! anywhere: every missing value has a plan.

Error or fine?
  • let nickname: string = null;
  • let nickname: string | null = null;
  • const box = document.querySelector("input"); box.value = "tea";
  • const box = document.querySelector("input")!; box.value = "tea";
  • const city = [{ city: "Pune" }].find((a) => a.city === "Goa").city;
  • const city = [{ city: "Pune" }].find((a) => a.city === "Goa")?.city;
  • function greet(name: string | undefined) {} greet();
  • function greet(name?: string) {} greet();
Try it yourself
0 of 8 correct

Sort each line by what TypeScript says about it, with strictNullChecks on. Each line is checked on its own.

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

Common mix-ups

Both null and undefined mean "missing" to you, but TypeScript keeps them apart:

Giving back undefined where null is expected (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
function findNickname(name: string): string | null {  return name === "Ravi" ? "Rav" : undefined;}

Line 1 says findNickname gives back a string or null. Line 2 gives back undefined when the name isn't Ravi, and TypeScript says Type 'undefined' is not assignable to type 'string | null'. Give back null instead, or write the type as string | null | undefined.

  • "null and undefined are the same type." No. They're two types. string | null doesn't accept undefined, and string | undefined doesn't accept null.
  • "The ! checks that the value is there." No. It's removed with the types. If the value is missing, the code throws the same TypeError as with no check at all.
  • "Turning strictNullChecks off fixes the error." No. It hides the warning. The value is still null when the code runs.
  • "?. handles the missing value for me." No. It gives undefined instead of throwing. You still decide what to show, often with ??.
  • "?? and || do the same thing." No. || also replaces 0, "", and false. ?? replaces only null and undefined.
  • "name?: string and name: string | undefined mean the same." No. Both give name the type string | undefined inside, but only the first lets a call leave it out.

Practice exercises

Each exercise is TypeScript. When you fix code, press Edit & run on the starter code: the errors update as you type, and the console shows what it prints. Try a guess first, then use the hints.

Exercise 1 · Warm-upRead the type of a page element

No type is written here. What type does TypeScript give couponBox? Guess first. Then point at couponBox, or press Edit & run and add a // ^? line under it.

Starter codePop out in the code editor (opens in a new tab)TypeScript
const couponBox = document.querySelector("#coupon");

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

    Exercise 2 · Warm-upFind the line TypeScript marks

    A user with an optional nickname. Which line will TypeScript mark?

    Starter codePop out in the code editor (opens in a new tab)TypeScript
    type User = { name: string; nickname?: string };
    
    const asha: User = { name: "Asha" };
    console.log(asha.nickname.toUpperCase());

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

      Exercise 3 · PracticeGive a missing theme a backup

      TypeScript marks line 4. Fix it so the theme is "light" when none is saved. What does it print?

      Starter codePop out in the code editor (opens in a new tab)TypeScript
      type Settings = { theme?: string }; const settings: Settings = {};const theme: string = settings.theme;console.log(theme);

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

        Exercise 4 · PracticeLet a call leave out the currency

        Line 6 leaves out the currency, and TypeScript marks it. Change line 1 so a left-out currency means EUR. What does priceLabel(3) print then?

        Starter codePop out in the code editor (opens in a new tab)TypeScript
        function priceLabel(price: number, currency: string) {  return `${price} ${currency}`;} console.log(priceLabel(3, "USD"));console.log(priceLabel(3));

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

          Exercise 5 · ChallengeReplace a risky !

          TypeScript reports nothing here. What happens when it runs on a page with no #total? Then remove the ! and print No total on this page instead.

          Starter codePop out in the code editor (opens in a new tab)TypeScript
          const totalBox = document.querySelector("#total")!;totalBox.textContent = "$7";console.log("Total shown");

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

            Exercise 6 · ChallengeMake a city label safe

            A profile may have no address, and an address may have a null city. Fix line 4 so every user gets a label. What does your code print for Mei?

            Starter codePop out in the code editor (opens in a new tab)TypeScript
            type Profile = { name: string; address?: { city: string | null } }; function cityLabel(user: Profile) {  return user.address.city.toUpperCase();} console.log(cityLabel({ name: "Asha", address: { city: "Pune" } }));console.log(cityLabel({ name: "Ravi", address: { city: null } }));console.log(cityLabel({ name: "Mei" }));

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

              Check your understanding

              For each question, keep two moments apart: what TypeScript says before the code runs, and what the code does when it runs. The quiz shows its code without the checker, so the error list doesn't give the answers away.

              The null and undefined quiz · 7 questionsScore: first tries count
              1. Question 1 of 7What type does TypeScript give params.get("sort")?

                Choose an answer to see the explanation.

              2. Question 2 of 7With strictNullChecks off, what happens with this code?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                const params = new URLSearchParams("?q=tea");
                const sort = params.get("sort");
                console.log(sort.length);

                Choose an answer to see the explanation.

              3. Question 3 of 7What does Ravi's city line print?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Profile = { name: string; address?: { city: string } };
                
                const ravi: Profile = { name: "Ravi" };
                console.log(ravi.address?.city ?? "No city");

                Choose an answer to see the explanation.

              4. Question 4 of 7What does this order line print, with a quantity of 0?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Order = { item: string; quantity?: number };
                
                const order: Order = { item: "Tea", quantity: 0 };
                console.log(order.quantity || 1, order.quantity ?? 1);

                Choose an answer to see the explanation.

              5. Question 5 of 7Which line does TypeScript flag in these greetings?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                function greet(name: string | undefined) {
                  return `Hello, ${name ?? "guest"}`;
                }
                
                greet("Asha");
                greet(undefined);
                greet();

                Choose an answer to see the explanation.

              6. Question 6 of 7What does the ! in sort!.toUpperCase() do when the code runs?

                Choose an answer to see the explanation.

              7. Question 7 of 7Does TypeScript accept this middle name?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                let middleName: string | null = undefined;

                Choose an answer to see the explanation.

              Key takeaways

              • A value that might be missing has null or undefined in its type, like string | null.
              • With strictNullChecks on (part of strict), TypeScript makes you handle the missing case before you use the value. Off, the bug is still there; only the warning is gone.
              • null and undefined are two different types. Browser functions like querySelector give null; find and optional properties give undefined.
              • Check with if (x !== null), read with ?., and give a backup with ??. TypeScript follows each one.
              • ! removes null and undefined from the type and checks nothing. Use it only when you're sure.
              • x?: T may be left out; x: T | undefined must be written, even as undefined.

              Remember the one-liner.
              If a value can be missing, its type says so, and TypeScript won't let you forget it.

              Coming next: Enums. They give a fixed set of choices names, like the sort orders a search page offers, and they're one of the few TypeScript features that still exist when the code runs.

              CompleteFrontend Clear concepts. Working examples.