cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

try, catch & finally

Catch runtime errors, inspect real Error objects, rethrow failures you cannot handle, and use finally without accidentally hiding a return or error.

By the end, you can
  • 01
    Trace protected codePredict exactly when try, catch, and finally run.
  • 02
    Read Error objectsUse name, message, stack, instanceof, and cause safely.
  • 03
    Handle only what you understandUse optional catch binding, rethrowing, and careful finally blocks.

The safety-net idea

JavaScript normally runs one statement after another. If a statement throws and nothing catches it, the current work stops and the error moves up the call stack. You already practiced reading those messages in Reading error messages, and you saw unwinding in The call stack. This lesson adds the tool that lets your program recover on purpose: try, catch, and finally.

The short version

Put risky runtime work in try. If it throws, handle the thrown value in catch. Put cleanup that must happen either way in finally.

Real-life analogyA safety net under a ladder

Imagine changing a light bulb with a safety net under the ladder. You carry on if you slip, then put the ladder away whether you slipped or finished safely. That is the shape of error handling.

In real life: Changing a light bulb
In JavaScript: The code inside try
In real life: Slipping from the ladder
In JavaScript: A thrown runtime error
In real life: The safety net
In JavaScript: The catch block
In real life: Putting the ladder away
In JavaScript: The finally block

Where the analogy stops: A real net catches a fall. A JavaScript catch only handles errors thrown while its matching try block is running. A later timer needs its own catch.

Use try/catch when failure is expected and recoverable: parsing user-provided JSON, reading optional browser storage, or handling a known validation problem. Do not wrap every line “just in case.” A catch block that hides unknown bugs makes debugging harder.

try, catch & finally

STEP THROUGH

Here is the whole flow in one tiny program. The path array records exactly which blocks run. Change what happens inside try, then step through the recorded run.

Trace the try/catch/finally path
Step 0 of 7Ready
Your turn: follow the blue line

Choose what happens inside try, predict the path, then step through the real run.

Running in
  1. script
Next: line 5
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
function riskyFormat(text) {  return text.toUpperCase();} let value = "not set"; try {  path.push("try");  value = riskyFormat("ok");} catch (error) {  path.push("catch " + error.name);  value = "fallback";} finally {  path.push("finally");} console.log(path.join(" → "));console.log(value);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose what happens inside try

Changing this starts a fresh recorded run. Predict the printed path before stepping.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
  1. Success: try runs, catch is skipped, finally runs.
  2. Handled error: try begins, a throw jumps to catch, then finally runs.
  3. Cleanup: finally is not “after catch.” It is “before leaving this try statement,” whether the path was success, catch, return, or throw.

This is runtime behavior. A SyntaxError can be thrown by JSON.parse at runtime, so a try block can catch it. But a syntax mistake in the same JavaScript file, such as an unclosed brace, prevents the file from being parsed; the try statement is never created.

The built-in error types

INTERACTIVE

Most thrown failures you meet are Error objects. Their most useful fields are name, message, and stack. The name is the category, such as TypeError. The message is a human-readable description. The stack is a string showing where the error was created and how calls got there; its exact format varies by browser.

Common built-in Error types
Error typeTypical causeSmall example
SyntaxErrorCode cannot be parsed, or JSON text is invalidJSON.parse("{broken}") inside a try block
ReferenceErrorA name is not defined in the current scopemissingValue
TypeErrorA value is the wrong kind for the operationnull.trim()
RangeErrorA number is outside the allowed rangenew Array(-1)
URIErrorURI encode/decode received malformed escape textdecodeURIComponent("%")
EvalErrorHistorical error type; rarely thrown by modern JavaScriptMostly seen in compatibility tables
AggregateErrorSeveral errors are reported togetherPromise.any(...) can create one in asynchronous code

Click the playground instead of trusting a fixed screenshot. It throws real errors in your browser and catches them immediately so you can inspect what this engine produced.

Inspect a real Error object
Inspector patternPop out in the code editor (opens in a new tab)JavaScript
try {  JSON.parse("{broken json}");} catch (error) {  console.log(error.name);  console.log(error.message);  console.log(error.stack?.split("\n")[0]);  console.log(error instanceof Error);  console.log(error.cause);}
Runtime resultnot run yet

Run the example to fill in name, message, stack, instanceof, and cause.

Try it yourself

Choose a real error, then run it. The code catches the thrown object and inspects its properties in your browser.

The stack format is intentionally computed on click to avoid server/browser mismatches and to show your actual engine.
Use reliable type checks

When you need to know whether a caught value is an error, start with error instanceof Error. The earlier Checking types reliably lesson covered instanceof and Error.isError. In ordinary app code, remember that JavaScript can throw any value, so check before reading message.

Optional catch binding

Sometimes the fact that something failed is enough. If you do not need the thrown value, JavaScript lets you omit the binding name: catch { ... }. This is called optional catch binding.

Use optional catch binding only when details do not matterPop out in the code editor (opens in a new tab)JavaScript
try {  JSON.parse(settingsText);} catch {  settings = defaultSettings;}

That shape is honest when every parse failure gets the same default. If you need to log, inspect error.name, show a specific message, or rethrow unknown failures, use catch (error).

Do not ignore useful errors

Optional binding is not “cleaner” by default. It is clearer only when the thrown value would be unused noise.

Rethrowing errors you cannot handle

CODE PATTERN

A catch block should handle a specific failure it understands. A small net under the ladder catches first; a bigger net catches what it lets through. In code, that handoff is rethrowing: throw error.

Real-life analogyThe small net and the bigger net

The small net handles the slip it was made for. If another problem reaches it, swallowing it would hide the problem. Let it continue to the bigger net instead.

In real life: The small net catches a slip
In JavaScript: A catch block handles ValidationError
In real life: A different problem slips through
In JavaScript: A bug such as TypeError
In real life: The bigger net catches it
In JavaScript: throw error reaches a caller or reporter

Where the analogy stops: Rethrowing does not magically fix the error. It preserves the failure so a better layer can log it, show a user-safe message, or crash loudly during development.

Handle one expected error; rethrow the restPop out in the code editor (opens in a new tab)JavaScript
class ValidationError extends Error {} function saveName(name) {  try {    validateName(name);    return "Saved";  } catch (error) {    if (!(error instanceof ValidationError)) {      throw error;    }    return "Please enter a name.";  }}

This lesson keeps ValidationError minimal so the pattern is visible. The next lesson, Throwing & custom errors, covers throw, extending Error, and cause in depth.

finally and return

STEP THROUGH

finally runs while JavaScript is leaving the try statement. That includes normal completion, a caught error, an uncaught error, and a return. The powerful part is also the dangerous part: a return or throw inside finally replaces what was already happening.

Finally return puzzles
Step 0 of 7Ready
Your turn: follow the blue line

Predict each puzzle before stepping. finally always runs; a return or throw inside it can replace what was already happening.

Running in
  1. script
Next: line 28
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
function returnFromTry() {  try {    return "try value";  } finally {    return "finally value";  }} function throwThenFinallyThrows() {  try {    throw new TypeError("try failed");  } finally {    throw new RangeError("cleanup failed");  }} function returnStillCleansUp() {  const steps = [];  try {    steps.push("try");    return steps.join(" → ");  } finally {    steps.push("finally");    console.log(steps.join(" → "));  }} try {  throwThenFinallyThrows();} catch (error) {  console.log(error.name + ": " + error.message);}console.log(returnStillCleansUp());
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Professional rule of thumb

Use finally for cleanup: close a resource, release a lock, stop a spinner, restore temporary state. Avoid returning from it. Avoid throwing from it unless that cleanup failure is truly the error you want callers to see.

What try/catch cannot catch

A catch block protects code that runs while the matching try block is active. Two common surprises fall outside that window.

Catchable vs not catchable by this try
SituationWill this catch run?Why
JSON.parse(badText) inside tryYesThe runtime throw happens while the try block is active.
A syntax mistake in the same scriptNoThe script fails before any statement runs.
A setTimeout callback throws laterNoThe callback runs in a later task after this try finished.
The timer callback has its own try/catchYesThat later try is active when the throw happens.
The outer catch is gone before the timer callback runsPop out in the code editor (opens in a new tab)JavaScript
try {  setTimeout(() => {    try {      throw new Error("late");    } catch (error) {      console.log("callback caught itself");    }  }, 0);} catch (error) {  console.log("outer catch ran");}console.log("try finished");

The code prints try finished, then the callback catches its own Error. Stage 6 covers promises and async/await, where asynchronous error handling gets its own rules.

Which blocks run?
  • No statement in try throws.
  • Line 10 throws TypeError while the function is running.
  • The same script contains try { with a missing closing brace.
  • A setTimeout callback throws after try has already finished.
  • try throws RangeError, and catch returns a fallback.
  • try starts to return a value.
  • JSON.parse succeeds inside try.
  • The timer callback has its own try/catch around the risky line.
Try it yourself
0 of 8 correct

Sort each scenario by the blocks that get a chance to run.

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

Where you will use this

The best try/catch blocks are small. Put only the risky operation in try, handle the known failure, and let the rest of the function stay normal.

  • Parsing: turn invalid JSON into a default instead of crashing a settings screen.
  • Validation: show a friendly message for expected bad input, then rethrow unknown bugs.
  • Cleanup: reset UI state in finally after a risky operation finishes.
  • Debugging: log name, message, and a stack when you cannot recover locally.
A careful JSON wrapper

Exercise 2 asks you to write this for real: parseOrDefault(text, fallback) should return parsed data when parsing succeeds and the fallback when parsing throws. Because every parse error is handled the same way, catch { ... } is appropriate there.

Common misconceptions

“Catch fixes the error automatically.”

No. It gives you a chance to respond. You still decide whether to use a fallback, show a message, log, or rethrow.

“Finally runs only after catch.”

It runs after the try attempt whether catch ran or not, and even while a return or throw is leaving.

“A try around setTimeout catches the callback later.”

The callback runs after the outer try has finished. Put a new try/catch inside the callback, or use the asynchronous patterns you will learn in Stage 6.

“Every thrown value is an Error.”

Most built-in failures are Error objects, but JavaScript allows throwing any value. Check before reading properties.

“Returning from finally is a tidy cleanup pattern.”

It is usually a bug because it replaces a return or error from the try block. Clean up without returning.

Practice: catching the right failures

5 EXERCISES
Exercise 1 · Warm-upPredict the execution path

Trace the program and enter the exact printed line.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const events = [];
try {
  events.push("try");
  throw new TypeError("boom");
} catch (error) {
  events.push(error.name);
} finally {
  events.push("finally");
}
console.log(events.join(" > "));

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

    Exercise 2 · PracticeWrite a safe JSON.parse wrapper

    Write parseOrDefault(text, fallback). It should return parsed JSON, or fallback when parsing fails.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function parseOrDefault(text, fallback) {
      try {
        return JSON.parse(text);
      } catch {
        return fallback;
      }
    }
    console.log(parseOrDefault("{\"count\":2}", { count: 0 }).count);
    console.log(parseOrDefault("broken", { count: 0 }).count);

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

      Exercise 3 · PracticeFix a finally that swallows a return

      The starter should return "Ada", but finally hides it. Change cleanup so the original return survives.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      function chooseName() {
        try {
          return "Ada";
        } finally {
          return "Grace";
        }
      }

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

        Exercise 4 · ChallengeRethrow what you cannot handle

        Keep the friendly validation fallback, but do not swallow unexpected errors.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        class ValidationError extends Error {}
        function readAge(value) {
          try {
            const age = Number(value);
            if (Number.isNaN(age)) throw new ValidationError("Age must be a number");
            if (age < 0) throw new RangeError("Age cannot be negative");
            return "Age: " + age;
          } catch (error) {
            if (!(error instanceof ValidationError)) throw error;
            return "Use digits only.";
          }
        }
        console.log(readAge("ten"));

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

          Exercise 5 · ChallengeExplain the setTimeout surprise

          Why does the outer catch not handle the late throw? Type the catch that does handle it.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          try {
            setTimeout(() => {
              try {
                throw new Error("late");
              } catch {
                console.log("callback caught itself");
              }
            }, 0);
          } catch {
            console.log("outer catch");
          }
          console.log("try finished");

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

            Quiz: check your understanding

            7 QUESTIONS

            Answer once from prediction, then read the explanation even when you were right.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7When does a catch block run?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does the successful try/finally example print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              try {
                console.log("A");
              } catch {
                console.log("B");
              } finally {
                console.log("C");
              }

              Choose an answer to see the explanation.

            3. Question 3 of 7Which property usually gives the error type, such as TypeError?

              Choose an answer to see the explanation.

            4. Question 4 of 7What does the finally return puzzle print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function puzzle() {
                try {
                  return "try";
                } finally {
                  return "finally";
                }
              }
              console.log(puzzle());

              Choose an answer to see the explanation.

            5. Question 5 of 7When is optional catch binding (catch { ... }) a good fit?

              Choose an answer to see the explanation.

            6. Question 6 of 7Why rethrow an error from catch?

              Choose an answer to see the explanation.

            7. Question 7 of 7What does this print first?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              try {
                setTimeout(() => console.log("timer"), 0);
              } catch {
                console.log("caught");
              }
              console.log("done");

              Choose an answer to see the explanation.

            Key takeaways

            • try protects runtime work; catch handles a thrown value from that work.
            • finally runs before leaving the try statement, even for returns and throws.
            • Error objects usually expose name, message, stack, and sometimes cause.
            • Use optional catch binding only when you truly ignore the thrown value.
            • Rethrow errors your catch block cannot responsibly handle.
            • A try block cannot catch syntax mistakes in the same script or errors thrown later in a timer callback.

            Remember the one-liner.
            A try block is protected work, catch is recovery, and finally is cleanup that runs on the way out.

            Up next: Throwing & custom errors.

            CompleteFrontend Clear concepts. Working examples.