cf.completefrontend
Open lab My progress
THE TYPESCRIPT FIELD GUIDE

Exhaustiveness checking

Make TypeScript tell you when a switch misses a case: never in the default branch, an assertNever helper, and return types, with switches you can break.

By the end, you can
  • 01
    Use never to find a missing casePut the leftover value where only never fits, so a case you forgot becomes an error.
  • 02
    Write and use assertNeverShare one helper that TypeScript checks while you type and that throws a clear error while the page runs.
  • 03
    Add a case safelyAdd a member to a union and follow TypeScript's errors to every switch that needs it, or use a return type to catch it.

A new order status that one switch forgot

A shop shows each order with a short label, like "On its way". A function picks the label with a switch. Then the team adds a new status, "cancelled", and nobody remembers this function:

A label for every status but onePop out in the code editor (opens in a new tab)JavaScript
function statusLabel(status) {
  switch (status) {
    case "placed":
      return "We got your order";
    case "shipped":
      return "On its way";
    case "delivered":
      return "At your door";
  }
}

console.log(statusLabel("cancelled"));

Lines 3 to 8 handle three statuses. Line 12 asks for the label of a cancelled order. No case matches, so the function reaches its end without a return and gives back undefined. The page prints undefined where the label should be, and nothing warns you.

In TypeScript you can ask the compiler to check that a switch handles every status. This is called exhaustiveness checking: "exhaustive" means "nothing left out". Here is the same function with the check added:

The same switch with a check (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type OrderStatus = "placed" | "shipped" | "delivered" | "cancelled"; function statusLabel(status: OrderStatus) {  switch (status) {    case "placed":      return "We got your order";    case "shipped":      return "On its way";    case "delivered":      return "At your door";    default:      const unhandled: never = status;      throw new Error(`Unknown status: ${unhandled}`);  }} console.log(statusLabel("cancelled"));

Line 1 is a union type: OrderStatus can be exactly one of four texts, and | means "or". Line 3 says status is an OrderStatus. Lines 11 to 13 are new. Line 12 puts status into a variable whose type is never. never is the type with no values: nothing is allowed to go in it.

Each case takes one status away. Whatever no case handled reaches default, and here that's "cancelled". TypeScript marks line 12, on purpose here, because it's the forgotten case:

  • Type '"cancelled"' is not assignable to type 'never' means "the value "cancelled" can still get here, and nothing is allowed here". The message names the case you forgot.
  • If you run the code anyway, line 13 throws Error: Unknown status: cancelled instead of showing undefined.
Four status cards: "placed", "shipped", "delivered", and "cancelled". The first three each go down to their own case and are handled. "cancelled" has no case, so it goes down to default, marked as an error.status can be"placed""shipped""delivered""cancelled"case "placed"case "shipped"case "delivered"defaulthandledhandledhandledleft over
Each case takes one status. "cancelled" has no case, so it reaches default, where only never may arrive. That's the error on line 12.
Real-life analogySorting the post into pigeonholes

In a shared flat, the post is sorted into pigeonholes, one per flatmate. At the end there's a tray marked "nothing should be here". When a new flatmate moves in and their first letter lands in that tray, you notice straight away and add a pigeonhole with their name.

In real life: Each pigeonhole has one flatmate's name on it
In TypeScript: Each case handles one status
In real life: A letter for a new flatmate has no pigeonhole yet
In TypeScript: "cancelled" has no case yet
In real life: It lands in the tray marked "nothing should be here"
In TypeScript: It reaches default, where only never may arrive
In real life: You spot it at once and add a pigeonhole
In TypeScript: TypeScript marks line 12, and you add the case
The main idea

Exhaustiveness checking makes TypeScript check that your code handles every member of a union. Each case narrows the value. After the last case, only never should be left. Put the value where only never fits, and any case you forgot becomes an error.

never in the default branch

never is the type with no values. No text, number, or object fits in it, so storing anything in a never variable is an error:

Nothing fits in never (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
const unhandled: never = "placed";

Line 1 tries to store "placed" in unhandled, and TypeScript says Type '"placed"' is not assignable to type 'never'. That's the error on purpose. So when is never useful? When a value has been checked so many times that no possibility is left.

TypeScript follows your checks. Inside case "placed":, it knows status is "placed", and after that case it knows status is something else. This is narrowing: the type gets smaller as your code checks it. When every case is handled, nothing is left:

Every status handled: default gets neverPop out in the code editor (opens in a new tab)TypeScript
type OrderStatus = "placed" | "shipped" | "delivered"; function statusLabel(status: OrderStatus) {  switch (status) {    case "placed":      return "We got your order";    case "shipped":      return "On its way";    case "delivered":      return "At your door";    default:      const unhandled: never = status;      //                       ^?      throw new Error(`Unknown status: ${unhandled}`);  }} console.log(statusLabel("shipped"));

Line 1 lists three statuses, and lines 5 to 10 handle all three. The query on line 13 points at status on line 12 and shows (parameter) status: never ("parameter" because status is the function's parameter). Nothing can reach default, so storing status in a never is fine, and there are no errors. Line 18 prints On its way.

Left: every status has a case, and the box unhandled, of type never, stays empty: fine. Right: "shipped" has no case. A card "shipped" points at the box unhandled, but cannot go in: an error.Every status has a caseunhandlednothing arrives: fineNo case for "shipped""shipped"unhandledcan't go in: error
unhandled is a box of type never, which no value fits in. With a case for every status, nothing reaches it. Leave out "shipped", and that value arrives at a box it can't go in.

Now take cases away and watch what reaches default. Each button adds or removes one case:

Playground: which statuses have a case
An order status switchPop out in the code editor (opens in a new tab)TypeScript
type OrderStatus = "placed" | "shipped" | "delivered"; function statusLabel(status: OrderStatus) {  switch (status) {    case "placed":      return "We got your order";    case "shipped":      return "On its way";    case "delivered":      return "At your door";    default:      const unhandled: never = status;      //                       ^?      throw new Error(`Unknown status: ${unhandled}`);  }}
What reaches defaultnever
What happens
Cases3 of 3
TypeScriptNo errors
Try it yourself

All three statuses have a case, so nothing is left for default. There, status is never, and const unhandled: never = status is fine. No errors.

Each button adds or removes one case. TypeScript checks the code for real each time, and the query under the never line shows what reaches default. Reset puts all three cases back.

Turn off case "shipped". The code gets two lines shorter, so the never line moves up to line 10, and the query under it shows "shipped": the one status no case handles. TypeScript marks line 10. Turn off a second case and the message lists both leftovers, like '"shipped" | "delivered"'. Turn off all three and the whole OrderStatus type reaches default. Turn them back on and the error goes away.

assertNever: one helper for every switch

Writing const unhandled: never = status; in every switch gets repetitive. Most projects write a small helper once, called assertNever, and call it from every default:

assertNever finds a missing status (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type OrderStatus = "placed" | "shipped" | "delivered" | "cancelled"; function assertNever(value: never): never {  throw new Error(`Unhandled value: ${JSON.stringify(value)}`);} function statusLabel(status: OrderStatus) {  switch (status) {    case "placed":      return "We got your order";    case "shipped":      return "On its way";    case "delivered":      return "At your door";    default:      return assertNever(status);  }}

Lines 3 to 5 are the helper. Its parameter value has the type never, so the only thing you may pass it is a value with nothing left. Its return type is never too, which means "this function never returns": line 4 always throws an error. Line 16 calls it from default.

Line 1 lists "cancelled", but no case handles it, so it reaches line 16. TypeScript marks that line, on purpose: Argument of type '"cancelled"' is not assignable to parameter of type 'never'. An argument is a value you pass to a function, so this means "you're passing "cancelled" to a function that accepts nothing". Add the case and the error goes away.

The helper works the same for a union of objects with a shared kind field, like the request states of a page that loads data:

A request state with no case for errors (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type RequestState =  | { kind: "loading" }  | { kind: "success"; items: string[] }  | { kind: "error"; message: string }; function assertNever(value: never): never {  throw new Error(`Unhandled value: ${JSON.stringify(value)}`);} function render(state: RequestState) {  switch (state.kind) {    case "loading":      return "Loading...";    case "success":      return `${state.items.length} items`;    default:      return assertNever(state);  }}

Lines 1 to 4 describe three states: loading, success with a list of items, and error with a message. Line 11 switches on state.kind, and each case narrows the whole state object. There's no case for "error", so line 17 gets an error on purpose. This time the message names the whole leftover object type: Argument of type '{ kind: "error"; message: string; }' is not assignable to parameter of type 'never'.

assertNever also helps while the page runs. Types are removed before the code runs, so data from outside, like a server reply read with JSON.parse, can hold a value your type doesn't list:

A status the type doesn't list, from a serverPop out in the code editor (opens in a new tab)TypeScript
type OrderStatus = "placed" | "shipped" | "delivered"; function assertNever(value: never): never {  throw new Error(`Unhandled value: ${JSON.stringify(value)}`);} function statusLabel(status: OrderStatus) {  switch (status) {    case "placed":      return "We got your order";    case "shipped":      return "On its way";    case "delivered":      return "At your door";    default:      return assertNever(status);  }} const order: { status: OrderStatus } = JSON.parse('{ "status": "returned" }');try {  console.log(statusLabel(order.status));} catch (error) {  console.log(String(error));}

Every status has a case, so TypeScript reports nothing. Line 20 reads an order from a server reply and says its status is an OrderStatus. TypeScript trusts that, because JSON.parse gives back any, which it never checks. But the reply says "returned". No case matches, so line 16 calls assertNever, which throws. The try and catch on lines 21 to 25 catch the error, and line 24 prints Error: Unhandled value: "returned". A clear error is much easier to find than a label that quietly says undefined.

Left, before it runs: an editor shows assertNever(status) with the note "cancelled" has no case, found while you type. Right, while it runs: a console shows Error: Unhandled value, because the server sent "returned".Before it runseditorassertNever(status)"cancelled" has no casefound while you typeWhile it runsconsoleError: Unhandled valuethe server sent "returned"a clear error, not a blank label
assertNever checks twice. While you type, TypeScript marks a status with no case. While the page runs, a value your type doesn't list, like "returned" from a server, makes it throw a clear error.

Adding a case safely

A real app has many switches over the same type: one picks the label, another the color, another the icon. When you add a status, the hard part is finding them all. With a check in each default, TypeScript finds them for you. Step through adding "cancelled":

Playground: add a cancelled status
Two switches over one status typePop out in the code editor (opens in a new tab)TypeScript
type OrderStatus = "placed" | "shipped" | "delivered"; function assertNever(value: never): never {  throw new Error(`Unhandled value: ${JSON.stringify(value)}`);} function statusLabel(status: OrderStatus) {  switch (status) {    case "placed": return "We got your order";    case "shipped": return "On its way";    case "delivered": return "At your door";    default: return assertNever(status);  }} function statusColor(status: OrderStatus) {  switch (status) {    case "placed": return "blue";    case "shipped": return "orange";    case "delivered": return "green";    default: return assertNever(status);  }}
TypeScriptNo errors
Still to update
FunctionsNothing
Step 1 of 4

Three statuses, and both switches handle all three. Each default hands assertNever a never, so TypeScript has nothing to report.

TypeScript checks the code for real at every step. Reset goes back to step 1.

Step 1 has no errors. Step 2 changes only line 1, and TypeScript marks line 12 in statusLabel and line 21 in statusColor: a to-do list of every switch that needs the new case. Step 3 adds the case to statusLabel on line 12, and one error is left, in statusColor. Step 4 adds it there too, and the errors are gone. Without the checks, step 2 would have no errors at all, and both functions would quietly give back undefined for a cancelled order.

Left: a window called type, holding | "cancelled", the status you add. Two arrows lead to two windows on the right, statusLabel and statusColor. Each shows assertNever(status) and an error: line 12 and line 21, not handled.type| "cancelled"you add one statusstatusLabelassertNever(status)line 12: not handledstatusColorassertNever(status)line 21: not handled
Add "cancelled" to the type, and every switch that doesn't handle it gets an error: line 12 in statusLabel and line 21 in statusColor. The errors are your to-do list.

Exhaustive return types

A switch that returns a value can be checked without any default at all. First, see what TypeScript works out when a case is missing and the function has no return type written:

A missing case adds undefined to the resultPop out in the code editor (opens in a new tab)TypeScript
type Shipping = "standard" | "express" | "pickup"; function deliveryDays(method: Shipping) {  switch (method) {    case "standard":      return 5;    case "express":      return 1;  }} let days = deliveryDays("pickup");//  ^?console.log(days);

Line 1 lists three ways to ship. Lines 5 to 8 handle two of them, and nothing handles "pickup". Line 12 asks for the pickup days, and the query on line 13 shows let days: number | undefined: TypeScript saw that the function can end without a return, so it can give back undefined. There's no error, and line 14 prints undefined.

Now write the return type you mean, : number, after the parameters. This is called a return type annotation:

A return type that the switch doesn't keep (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Shipping = "standard" | "express" | "pickup"; function deliveryDays(method: Shipping): number {  switch (method) {    case "standard":      return 5;    case "express":      return 1;  }}

Line 3 promises a number. With no case for "pickup", the function can still reach its end without returning, so TypeScript marks line 3, on purpose: Function lacks ending return statement and return type does not include 'undefined'. In plain words: "some path through this function returns nothing, and number doesn't allow that". Add case "pickup": return 0; and the error goes away. Exercise 6 asks you to do it.

The table below compares the ways to end a switch. Read each row as: this ending, what TypeScript says when a status has no case, and what happens if a value the type doesn't list arrives while the page runs.

Four ways to end a switch over OrderStatus
How the switch endsWhen a status has no caseWhen an unlisted value arrives while the page runs
default: with const unhandled: never = status;An error on that line: Type '"cancelled"' is not assignable to type 'never'.The next line throws an error, if you write one.
default: return assertNever(status);An error on that line: Argument of type '"cancelled"' is not assignable to parameter of type 'never'.assertNever throws an error that names the value.
No default, and a return type like : numberAn error on the return type: Function lacks ending return statement.The function gives back undefined, and nothing warns you.
default: return "Unknown status";No error. The fallback answers for every case you forgot.The page shows the fallback text.

Every payment method at checkout

A checkout page shows a short summary of how the shopper is paying. Each payment method carries different details, so the payment is a union of objects with a shared method field:

A payment summary that handles every methodPop out in the code editor (opens in a new tab)TypeScript
type Payment =  | { method: "card"; last4: string }  | { method: "paypal"; email: string }  | { method: "giftCard"; balance: number }; function assertNever(value: never): never {  throw new Error(`Unhandled value: ${JSON.stringify(value)}`);} function paymentSummary(payment: Payment) {  switch (payment.method) {    case "card":      return `Card ending ${payment.last4}`;    case "paypal":      return `PayPal (${payment.email})`;    case "giftCard":      return `Gift card, $${payment.balance} left`;    default:      return assertNever(payment);  }} console.log(paymentSummary({ method: "card", last4: "4242" }));console.log(paymentSummary({ method: "giftCard", balance: 15 }));

Lines 1 to 4 describe three methods: a card with its last four digits, PayPal with an email, and a gift card with a balance. Line 11 switches on payment.method. Inside each case TypeScript knows which object it has, so line 13 can read last4 and line 17 can read balance. Line 19 hands the leftover to assertNever, and since every method has a case, there's no error. Line 23 prints Card ending 4242 and line 24 prints Gift card, $15 left.

Next month the shop adds Apple Pay. Add | { method: "applePay" } to the type with Edit & run, and line 19 gets an error until you add its case.

Caught or missed?
  • The switch ends with default: return assertNever(status);
  • The switch ends with default: and const unhandled: never = status;
  • No default, and the function says it returns : string
  • Three if checks, then return assertNever(status);
  • The switch ends with default: return "Unknown status";
  • No default, and no return type
  • The switch ends with default: throw new Error("Unknown status");
  • status is a plain string, with a fallback default
Try it yourself
0 of 8 correct

The type lists "cancelled", and the statusLabel function has no case for it. Sort each way of writing the function by whether TypeScript points at the missing case.

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

Common mix-ups

A default branch alone doesn't make a switch safe. Here are two statuses forgotten, with no error at all:

A fallback default hides missing casesPop out in the code editor (opens in a new tab)TypeScript
type OrderStatus = "placed" | "shipped" | "delivered" | "cancelled"; function statusLabel(status: OrderStatus) {  switch (status) {    case "placed":      return "We got your order";    case "shipped":      return "On its way";    default:      return "Unknown status";  }} console.log(statusLabel("delivered"));

Line 1 lists four statuses, and lines 5 to 8 handle only two. Line 10 returns "Unknown status" for anything else, which TypeScript accepts, because a fallback can stand for any status. Line 14 asks about a delivered order and prints Unknown status.

The check also needs a union with a fixed list of members. A plain string is never used up:

A string can't be checked case by case (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
function statusLabel(status: string) {  switch (status) {    case "placed":      return "We got your order";    case "shipped":      return "On its way";    case "delivered":      return "At your door";    default:      const unhandled: never = status;      throw new Error(`Unknown status: ${unhandled}`);  }}

Line 1 says status is any string. Lines 3 to 8 handle three texts, but a string can be endless other texts, so it's still a string in default. Line 10 gets Type 'string' is not assignable to type 'never' on purpose, however many cases you add. Give status a union type, like OrderStatus, to make the check work.

  • "A default branch makes a switch safe." No. A default that returns a fallback answers for every forgotten case, so TypeScript has nothing to report. Put the check in default instead.
  • "never means null or undefined." No. undefined is a value. never has no values at all: it's the type of a place your code can't reach.
  • "TypeScript checks every switch for missing cases on its own." No. It only reports a missing case when you ask: with a never variable, assertNever, or a return type.
  • "The check works on any type." No. It needs a union with a fixed list of members, like "placed" | "shipped". A string or a number is never used up.
  • "assertNever only matters while I type." No. It also throws while the page runs, when outside data has a value your type doesn't list, like "returned".

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-upFind the line TypeScript marks

A theme switch with one theme forgotten. Which line will TypeScript mark?

Starter codePop out in the code editor (opens in a new tab)TypeScript
type Theme = "light" | "dark" | "system";

function assertNever(value: never): never {
  throw new Error(`Unhandled value: ${JSON.stringify(value)}`);
}

function themeIcon(theme: Theme) {
  switch (theme) {
    case "light":
      return "sun";
    case "dark":
      return "moon";
    default:
      return assertNever(theme);
  }
}

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

    Exercise 2 · Warm-upRead the type in default

    Every theme has a case. What type does theme have in default? Guess first. Then point at theme at the end of line 12 to check.

    Starter codePop out in the code editor (opens in a new tab)TypeScript
    type Theme = "light" | "dark" | "system"; function themeIcon(theme: Theme) {  switch (theme) {    case "light":      return "sun";    case "dark":      return "moon";    case "system":      return "monitor";    default:      const unhandled: never = theme;      throw new Error(`Unknown theme: ${unhandled}`);  }}

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

      Exercise 3 · PracticeHandle the new button style

      A designer added a red "danger" button. Make buttonClass("danger") return "btn btn-red", and change the last line to print it. What does it print?

      Starter codePop out in the code editor (opens in a new tab)TypeScript
      type ButtonVariant = "primary" | "secondary" | "danger"; function assertNever(value: never): never {  throw new Error(`Unhandled value: ${JSON.stringify(value)}`);} function buttonClass(variant: ButtonVariant) {  switch (variant) {    case "primary":      return "btn btn-blue";    case "secondary":      return "btn btn-grey";    default:      return assertNever(variant);  }} console.log(buttonClass("primary"));

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

        Exercise 4 · PracticeWrite assertNever

        Line 12 calls assertNever, but nobody wrote it. Write the helper above sizeLabel. What return type does it have?

        Starter codePop out in the code editor (opens in a new tab)TypeScript
        type Size = "S" | "M" | "L"; function sizeLabel(size: Size) {  switch (size) {    case "S":      return "Small";    case "M":      return "Medium";    case "L":      return "Large";    default:      return assertNever(size);  }} console.log(sizeLabel("M"));

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

          Exercise 5 · ChallengeCheck every notification

          A notification bell shows three kinds of notice. TypeScript reports nothing. What does the starter print? Then add a check so TypeScript finds the missing kind, and handle it.

          Starter codePop out in the code editor (opens in a new tab)TypeScript
          type Notice =  | { kind: "message"; from: string }  | { kind: "sale"; percent: number }  | { kind: "shipped"; orderId: number }; function noticeText(notice: Notice) {  switch (notice.kind) {    case "message":      return `New message from ${notice.from}`;    case "sale":      return `${notice.percent}% off today`;  }} console.log(noticeText({ kind: "shipped", orderId: 42 }));

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

            Exercise 6 · ChallengeKeep a return type's promise

            This function has no default, and its return type catches the missing method. Fix it so pickup takes 0 days. What does deliveryDays("pickup") print?

            Starter codePop out in the code editor (opens in a new tab)TypeScript
            type Shipping = "standard" | "express" | "pickup"; function deliveryDays(method: Shipping): number {  switch (method) {    case "standard":      return 5;    case "express":      return 1;  }}

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

              Check your understanding

              For each question, ask two things: which members of the union reach default, 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 exhaustiveness checking quiz · 7 questionsScore: first tries count
              1. Question 1 of 7Every status has a case. What type does status have in the default branch?

                Choose an answer to see the explanation.

              2. Question 2 of 7Which line does TypeScript flag in this tab switch?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Tab = "home" | "search" | "profile";
                
                function assertNever(value: never): never {
                  throw new Error(`Unhandled value: ${JSON.stringify(value)}`);
                }
                
                function tabTitle(tab: Tab) {
                  switch (tab) {
                    case "home":
                      return "Home";
                    case "profile":
                      return "Your profile";
                    default:
                      return assertNever(tab);
                  }
                }

                Choose an answer to see the explanation.

              3. Question 3 of 7This switch has no check in it. What does it print, and does TypeScript complain?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Tab = "home" | "search" | "profile";
                
                function tabTitle(tab: Tab) {
                  switch (tab) {
                    case "home":
                      return "Home";
                    case "profile":
                      return "Your profile";
                  }
                }
                
                console.log(tabTitle("search"));

                Choose an answer to see the explanation.

              4. Question 4 of 7What type does TypeScript name in the error on line 8?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Tab = "home" | "search" | "profile";
                
                function tabTitle(tab: Tab) {
                  switch (tab) {
                    case "home":
                      return "Home";
                    default:
                      const unhandled: never = tab;
                      throw new Error(`Unknown tab: ${unhandled}`);
                  }
                }

                Choose an answer to see the explanation.

              5. Question 5 of 7Every tab has a case, yet TypeScript flags line 10. Why?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                function tabTitle(tab: string) {
                  switch (tab) {
                    case "home":
                      return "Home";
                    case "search":
                      return "Search";
                    case "profile":
                      return "Your profile";
                    default:
                      const unhandled: never = tab;
                      throw new Error(`Unknown tab: ${unhandled}`);
                  }
                }

                Choose an answer to see the explanation.

              6. Question 6 of 7A saved tab comes back from storage as "settings". What does this print?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Tab = "home" | "search" | "profile";
                
                function assertNever(value: never): never {
                  throw new Error(`Unhandled value: ${JSON.stringify(value)}`);
                }
                
                function tabTitle(tab: Tab) {
                  switch (tab) {
                    case "home":
                      return "Home";
                    case "search":
                      return "Search";
                    case "profile":
                      return "Your profile";
                    default:
                      return assertNever(tab);
                  }
                }
                
                const saved: Tab = JSON.parse('"settings"');
                try {
                  console.log(tabTitle(saved));
                } catch (error) {
                  console.log(String(error));
                }

                Choose an answer to see the explanation.

              7. Question 7 of 7Why does assertNever take a parameter of type never?

                Choose an answer to see the explanation.

              Key takeaways

              • Each case narrows a union. After the last case, the value's type is never: nothing is left.
              • Put the leftover where only never fits, and a forgotten case becomes an error that names it.
              • assertNever(value: never): never does that in one shared helper, and throws a clear error if an unlisted value arrives while the page runs.
              • Add a member to a union, and the errors list every switch that still needs a case.
              • A return type like : number also catches a missing case in a switch with no default.
              • A fallback default or a plain string type turns the check off.

              Remember the one-liner.
              Handle every case, and never is all that's left; forget one, and TypeScript tells you which.

              Coming next: Type predicates & assertion functions. You'll write your own checks, like isUser(value), that narrow types the way case does here.

              CompleteFrontend Clear concepts. Working examples.