2.5 Handling Errors with try/catch
SLO 2
Describe the principles of structured programming.
A caught error is a second path through the code, not an escape from it: try, catch and finally each have a defined turn, and finally runs whichever way the block ended. That is cleanup you can rely on without reading every branch above it.
SLO 4
Explain what an algorithm is and its importance in computer programming.
The fit here is indirect — this is less about writing an algorithm than about what happens when one meets input it was not written for. Throwing your own error states a precondition out loud, and syntax errors mark the limit: only a running program can be rescued.
Learning Objectives
After this section, you will be able to:
- Explain what happens to a running program when an error occurs.
- Tell a syntax error apart from a runtime error, and say why only one of them can be handled.
- Write a
try...catchblock and predict which lines run in each case. - Read an error object's
nameandmessageto say what went wrong. - Raise your own error with
throwwhen the data is wrong but the code is not. - Use
finallyfor cleanup that has to happen either way.
2.5.1 What an Error Actually Does
Up to now, an error has meant your program did not work and you fixed it. This section is about the errors you cannot fix in advance — the ones caused by something outside your program being wrong at the moment it runs.
First, be clear about what an error does to a running program. It does not "return false", and it does not skip a line and carry on. It stops everything.
▶ Press Run to see the output…
What you should see:
Line 1 runs.
...followed by an error message naming total, which was never declared. The third line does not run. It is not that the third line failed — it was never reached.
That distinction matters, because it means an error anywhere in a long program throws away everything that was going to happen after it.
Two kinds of error
// SYNTAX ERROR — this is not JavaScript at all let x = ;
JavaScript reads your whole program before running any of it. If it cannot make sense of the text, nothing runs — not even the correct lines above the mistake. That is a syntax error, and there is nothing to handle: the program never started.
▶ Press Run to see the output…
What you should see:
Price is 10
...and then an error. This program is perfectly valid JavaScript. It parsed, it started, it ran two lines, and then it hit something wrong. That is a runtime error, and it is the kind this section is about.
The practical difference is whose fault it is. A syntax error is always yours, and you fix it by fixing the code. A runtime error is often nobody's fault — a file that is not there, a server that did not answer, a user who typed letters into a number box. You cannot fix those in advance, which is exactly why JavaScript gives you a way to handle them when they happen.
A runtime error is an error that occurs while a program is running, in code the engine understood well enough to start executing. It stops execution at that point. A syntax error, by contrast, is caught before the program starts and prevents it from running at all.
Definition 2.5.1 — A runtime error is an error that occurs while a program is running, in code the engine understood well enough to start executing; it stops execution at that point, unlike a syntax error, which is caught before the program starts and prevents it from running at all.
1. Which of these is a runtime error?
let total = ;console.log(mystery);wheremysterywas never declaredif (x > 5 {with a missing parenthesis
Solution
b. The code is valid JavaScript, so it runs — and fails at the moment it tries to read a variable that does not exist.
a and c are both syntax errors. JavaScript cannot even read them, so the program never starts and there is nothing to catch.
2.5.2 Catching an Error with try...catch
A try...catch block lets you say: attempt this, and if it goes wrong, do that instead.
▶ Press Run to see the output…
What you should see:
Trying... Something went wrong, but we recovered. The program keeps going.
Three things to notice, and the third is the point of the whole section:
- The line after the error inside
trydid not run.trydoes not resume where it failed — it abandons the rest of the block. - The
catchblock ran instead. - The program continued afterwards. Without the
try...catch, the last line would never have printed.
When nothing goes wrong, catch is skipped entirely:
▶ Press Run to see the output…
What you should see:
Trying... All fine. Done.
A try...catch statement runs the code in its try block, and if a runtime error occurs there, immediately abandons the rest of that block and runs the catch block instead. Execution then continues normally after the statement. If no error occurs, the catch block is skipped.
Definition 2.5.2 — A try...catch statement runs the code in its try block, and if a runtime error occurs there, immediately abandons the rest of that block and runs the catch block instead; execution then continues normally after the statement.
The name in parentheses after catch — err above — is yours to choose. It holds a value describing what went wrong, which the next subsection unpacks.
It is tempting to wrap a whole program in one try. Resist it. A try block abandons everything below the failure, so a large block loses work that had nothing to do with the error.
▶ Press Run to see the output…
Solution
▶ Press Run to see the output…
What you should see:
No sensor name available, using a default. Total: 38
The loop is outside the try, because a loop over a list you already have cannot fail. Only the risky line is wrapped. Had the whole program been inside the try, the total would have been thrown away along with the error.
A catch block that does nothing is worse than no try...catch at all. It converts a loud, informative crash into a program that silently produces the wrong answer — the hardest kind of bug to find. If you catch an error, either fix the situation, use a sensible default, or at minimum say what happened.
1. What does this print?
▶ Press Run to see the output…
A B C DA C DA C
Solution
b. A, C, D.
B is skipped because the error abandoned the rest of the try block. C runs because catch handles it. D runs because a caught error does not stop the program.
2. How many lines does this print?
▶ Press Run to see the output…
- Two
- Three
- One
Solution
a. Two. Nothing went wrong, so catch is skipped entirely. catch is not an "afterwards" block — it is an "instead" block.
Write a try...catch that attempts to print a variable named username that you never declared. In the catch, print "Falling back to Guest". After the whole block, print "Welcome!" and confirm it appears.
▶ Press Run to see the output…
Solution
▶ Press Run to see the output…
Output:
Falling back to Guest Welcome!
"Welcome!" printing is the proof that the error was handled rather than fatal.
2.5.3 Reading the Error Object
When JavaScript raises an error, it hands catch a value describing it. That value has two properties worth knowing, read with the dot notation you met in Section 1.2:
name— the kind of error, like"ReferenceError"or"TypeError".message— a sentence describing what specifically went wrong.
▶ Press Run to see the output…
What you should see:
Kind: ReferenceError Details: nothingHere is not defined
Different mistakes produce different kinds:
▶ Press Run to see the output…
What you should see:
Kind: TypeError Details: Cannot read properties of null (reading 'length')
A ReferenceError means you named something that does not exist. A TypeError means the thing exists but is not the kind of thing you tried to use it as. Those two cover most of what a beginner meets.
Read the message before you change anything. nothingHere is not defined tells you the exact name JavaScript could not find — which is usually a typo, and usually a typo you will stare straight past in your own code. The error already did the searching for you.
Compare a catch that says nothing useful with one that reports.
▶ Press Run to see the output…
Solution
▶ Press Run to see the output…
What you should see:
Error! Could not read the setting: config is not defined
Both programs survived. Only one of them tells you what to fix.
Write a try...catch that causes a TypeError on purpose, and print both err.name and err.message. (Hint: reading a property of null will do it.)
▶ Press Run to see the output…
Solution
▶ Press Run to see the output…
Output:
TypeError Cannot read properties of null (reading 'name')
2.5.4 Throwing Your Own Errors
Everything so far handled errors JavaScript raised. You can raise your own, with throw.
Why would you want to? Because "wrong" is often about your rules, not JavaScript's. An age of -5 breaks nothing in the language. It is still wrong.
▶ Press Run to see the output…
What you should see:
Rejected: Age cannot be negative.
throw behaves exactly like an error JavaScript raised itself: the rest of the try block is abandoned and catch takes over. new Error("...") builds the error value, and the text you pass becomes its message.
The throw statement raises an error deliberately. Execution stops at that point and jumps to the nearest enclosing catch, exactly as it would for an error the engine raised on its own.
Definition 2.5.3 — The throw statement raises an error deliberately; execution stops at that point and jumps to the nearest enclosing catch, exactly as it would for an error the engine raised on its own.
A quantity that arrived as text has to survive two separate checks before the program should use it.
▶ Press Run to see the output…
Solution
▶ Press Run to see the output…
What you should see:
Could not place the order. That is not a number: abc
Try "0" and then "3" in place of "abc" to walk all three paths. Note that Number("abc") does not throw on its own — it quietly produces NaN, which is precisely the sort of silent wrongness a deliberate throw turns into something you cannot ignore.
There is a real judgement call here: throw, or handle it inline with an if? A useful rule is distance. If the code that notices the problem is also the code that knows what to do about it, an if is simpler and clearer. throw earns its place when the code that notices cannot decide — validation deep inside a calculation has no business printing a message to the user, so it throws, and code further out decides what the user sees.
1. What does this print?
▶ Press Run to see the output…
start/after the throw/caught: stop right herestart/caught: stop right herecaught: stop right here
Solution
b. start then caught: stop right here.
throw abandons the rest of the try block immediately, so after the throw is unreachable — exactly like the line after a runtime error.
2. Why throw an error for an age of -5, when JavaScript is perfectly happy with negative numbers?
- Because JavaScript cannot store negative numbers reliably.
- Because the value is invalid according to the program's rules, not the language's.
- Because
throwis required whenever you useif.
Solution
b. The language has no opinion about ages. Your program does, and throw is how a program states a rule the language does not know about.
Write a try...catch that checks a variable password. If it is shorter than 8 characters, throw an error saying so; otherwise print "Password accepted". Test it with "abc".
▶ Press Run to see the output…
Solution
▶ Press Run to see the output…
Output:
Password must be at least 8 characters.
Change password to "correcthorse" and it prints Password accepted instead.
2.5.5 Cleaning Up with finally
Sometimes a step has to happen whether things went well or badly: closing a file, hiding a loading spinner, releasing something you reserved. A finally block runs either way.
▶ Press Run to see the output…
What you should see:
Opening the connection. Handling: missingSetting is not defined Closing the connection.
Now the same block with nothing going wrong:
try...catch is a branch you never wrote. Drawing it puts the invisible diamond on the page.
Figure 2.5.1 — try, catch and finally. The diamond is a question nobody typed, and finally is the one box both answers reach.
Three things the two listings above only hint at. The diamond is not a line of code -- it is asked after *every* statement in the try. The yes branch abandons the rest of the try block, which is why this section says to wrap only what can fail. And finally is the single box both branches arrive at, which is what "runs either way" means as a shape.
try...catch is a branch you never wrote. Drawing it puts the invisible diamond on the page.
Three things the two listings above only hint at. The diamond is not a line of code -- it is asked after every statement in the try. The yes branch abandons the rest of the try block, which is why this section says to wrap only what can fail. And finally is the single box both branches arrive at, which is what "runs either way" means as a shape.
▶ Press Run to see the output…
What you should see:
Opening the connection. Work done. Closing the connection.
The catch ran in one case and not the other. finally ran in both.
"But I could just write the cleanup line after the whole try...catch" — and often you can. The difference shows when the catch does not handle everything, because an error escaping the catch skips the lines below the block, and still runs finally. Reach for finally when the cleanup must happen even if the error handling itself fails.
A finally block attached to a try...catch runs after the statement finishes, whether or not an error occurred and whether or not it was caught. It is used for cleanup that must happen in either case.
Definition 2.5.4 — A finally block attached to a try...catch runs after the statement finishes, whether or not an error occurred and whether or not it was caught; it is used for cleanup that must happen in either case.
1. Which blocks run when the try block succeeds?
tryandcatchtryandfinally- All three
Solution
b. try and finally. catch runs only when something went wrong; finally runs regardless.
2.5.6 What try...catch Cannot Do
try...catch is not a universal safety net, and knowing its two limits saves hours.
It cannot catch a syntax error. As Section 2.5.1 explained, JavaScript reads the whole program before running any of it. If the text does not parse, nothing runs — including your try.
// This does NOT protect anything. The file never starts.
try {
let x = ;
} catch (err) {
console.log("never reached");
}
The try block is not skipped over here; the entire program fails to load. There is no running program in which the catch could fire.
**It only catches errors from code that runs now.** A try...catch guards the lines inside it, at the moment they execute. Code that is scheduled to run later has left the block behind by the time it runs, so an error in it is not caught. You will meet that case in Section 9.3, once you have the tools to schedule work.
▶ Press Run to see the output…
What you should see:
Inside try.
...and then an uncaught error, because the last line is outside the block. That is the same rule stated the simplest way: a try...catch protects what is inside it, and nothing else.
1. Why can try...catch not handle a missing closing brace?
- Because braces are not errors.
- Because the program never starts — the engine fails while reading the code.
- Because you must use
finallyfor braces.
Solution
b. A syntax error is found before execution begins, so there is no running program in which a catch could fire.
Predict the output, then run it to check.
try {
console.log("A");
throw new Error("boom");
} catch (err) {
console.log("B: " + err.message);
} finally {
console.log("C");
}
console.log("D");
Type it into the editor and run it:
▶ Press Run to see the output…
Solution
Output:
A B: boom C D
A runs, the throw abandons the rest of try, catch prints B, finally always runs and prints C, and D runs because the error was handled and the program continued.
Problem Set 2.5
2.5.1 Which of these is a runtime error, and why are the other two not?
let total = ;console.log(mystery);wheremysterywas never declaredif (x > 5 {
Solution
Step 1 — Identify the runtime error: Option b is the runtime error. console.log(mystery); is valid JavaScript, so it parses and starts running — and only fails at the moment it tries to read a variable that was never declared.
Step 2 — Explain why a is not: let total = ; is not JavaScript at all. The engine cannot parse it, so this is a syntax error and nothing runs.
Step 3 — Explain why c is not: if (x > 5 { has a missing closing parenthesis. The engine fails while reading the code, so this is also a syntax error, not something that happens while running.
Answer: b is the runtime error; a and c are syntax errors because they fail before the program ever starts.
2.5.2 What does this print?
▶ Press Run to see the output…
Solution
Step 1 — Trace the try block: "A" prints, then console.log(missing) raises a ReferenceError because missing was never declared.
Step 2 — Apply the abandon rule: A runtime error abandons the rest of the try block, so "B" never prints.
Step 3 — Run catch and continue: The error jumps to catch, which prints "C". Because the error was handled, execution continues normally after the statement, so "D" prints too.
Answer: It prints A, then C, then D.
2.5.3 How many lines does this print, and why is catch not one of them?
▶ Press Run to see the output…
Solution
Step 1 — Trace the try block: Both lines succeed: "one" and "two" print. Nothing goes wrong.
Step 2 — Apply the no-error rule: When nothing goes wrong, the catch block is skipped entirely. catch is an "instead" block, not an "afterwards" block — it only runs in place of the abandoned remainder of try.
Answer: Two lines print (one and two). catch does not run because there was no error to handle.
2.5.4 What does this print? Explain what happens to the line after the throw.
▶ Press Run to see the output…
Solution
Step 1 — Trace up to the throw: "start" prints. Then throw new Error("stop right here") raises an error deliberately.
Step 2 — What happens after the throw: Execution stops immediately and jumps to the nearest enclosing catch. The line console.log("after the throw"); is unreachable — it is abandoned exactly like the line after any runtime error.
Step 3 — Run catch: The catch receives the error object, and its message property holds the text passed to new Error(...), so it prints caught: stop right here.
Answer: It prints start then caught: stop right here. The line after the throw never runs, because throw abandons the rest of the try block immediately.
2.5.5 Why would a program throw an error for an age of -5 when JavaScript accepts negative numbers happily?
Solution
Step 1 — Distinguish whose rules are broken: JavaScript has no opinion about ages — negative numbers are perfectly valid values in the language, so no engine-raised error will ever occur.
Step 2 — State the program's own rule: An age of -5 breaks the program's rules, not the language's. throw is how a program states a rule the language does not know about, turning silent wrong data into an error that cannot be ignored.
Answer: Because -5 is invalid according to the program's rules even though the language accepts it; throw enforces that rule explicitly.
2.5.6 Which blocks run when the try block succeeds, and which run when it fails?
Solution
Step 1 — Success case: When the try block succeeds, the try block runs fully and the finally block runs afterwards. The catch block is skipped entirely.
Step 2 — Failure case: When the try block fails, the rest of try is abandoned, the catch block runs instead, and then finally still runs.
Answer: On success: try and finally. On failure: catch and finally. finally runs either way — that is its purpose.
2.5.7 Explain in one sentence why try...catch cannot handle a missing closing brace.
Solution
Step 1 — Recall when syntax errors are found: A missing closing brace means the code cannot be parsed. JavaScript reads the whole program before running any of it.
Step 2 — Draw the conclusion: Since the program never starts, there is no running program in which a catch could fire — including your try...catch.
Answer: A missing brace is a syntax error caught while the engine reads the code, so the program never starts and no catch can run.
2.5.8 Write a try...catch that reads an undeclared variable and prints both err.name and err.message.
▶ Press Run to see the output…
Solution
Step 1 — Wrap the risky line: Reading an undeclared variable raises a ReferenceError, so put it inside try and report both properties of the error object using dot notation.
▶ Press Run to see the output…
Output:
ReferenceError ghostVariable is not defined
Answer: The code above prints ReferenceError followed by ghostVariable is not defined — the kind of error and what specifically went wrong.
2.5.9 Write code that causes a TypeError on purpose and reports its message.
▶ Press Run to see the output…
Solution
Step 1 — Cause the TypeError deliberately: Reading a property of null raises a TypeError, because null exists but is not the kind of thing you can read .name from.
Step 2 — Report the message: Catch into err and print err.message.
▶ Press Run to see the output…
Output:
That went wrong: Cannot read properties of null (reading 'name')
Answer: The code above deliberately causes a TypeError by reading a property of null and reports its message.
2.5.10 Write a try...catch that checks a variable password and throws when it is shorter than 8 characters.
▶ Press Run to see the output…
Solution
Step 1 — Set up the data and the check: Give password a short value, open a try, and compare its length against 8.
Step 2 — Throw on failure, accept otherwise: If the check fails, throw with a clear message; otherwise print acceptance.
▶ Press Run to see the output…
Output:
Password must be at least 8 characters.
Answer: With "abc" the code throws and prints Password must be at least 8 characters.; with a password of 8 or more characters it prints Password accepted.
2.5.11 Explain why an empty catch block — catch (err) { } — can be worse than having no try...catch at all.
Solution
Step 1 — Compare loud vs silent failure: Without a try...catch, an error crashes loudly with a message naming the problem — annoying but findable.
Step 2 — Explain the empty catch's harm: An empty catch swallows the error completely. The program keeps going as if nothing happened, silently producing a wrong answer instead of crashing. There is no error message, no stack trace, and no clue where to look — the hardest kind of bug to find.
Answer: An empty catch converts an informative crash into a program that silently produces wrong results, hiding the bug entirely.
2.5.12 Rewrite this so the total is still printed when the risky line fails. (Hint: the try is wrapped around too much.)
try {
let total = 0;
for (let i = 1; i <= 4; i++) {
total = total + i;
}
console.log(label);
console.log("Total: " + total);
} catch (err) {
console.log("Failed.");
}
▶ Press Run to see the output…
Solution
Step 1 — Diagnose the problem: In the original, everything — including the loop that computes the total — sits inside the try. When console.log(label) fails, the whole block is abandoned and the total is thrown away along with the error.
Step 2 — Wrap only what can fail: Move the loop outside the try, since summing numbers you already have cannot fail. Only the risky line stays inside.
▶ Press Run to see the output…
Output:
No label available. Total: 10
Answer: With the loop outside the try, the output is No label available. followed by Total: 10 — the work that had nothing to do with the error survives.
2.5.13 A variable input holds the text "12abc". Write a try...catch that converts it with Number(input), throws when the result is NaN, and reports the problem. What does Number("12abc") actually produce?
▶ Press Run to see the output…
Solution
Step 1 — Convert and check for NaN: Number(input) does not throw on bad text — it quietly produces NaN. So convert first, then test with Number.isNaN and throw if it failed.
Step 2 — Handle the thrown error: Catch and report the message.
▶ Press Run to see the output…
Output:
Problem: Could not convert '12abc' to a number.
Step 3 — Answer the question about Number: Number("12abc") produces NaN ("Not a Number"). It does not raise an error, which is exactly why you must check for NaN yourself and turn the silent wrongness into a deliberate throw.
Answer: The code above reports the conversion failure, and Number("12abc") produces NaN rather than throwing.
2.5.14 Give one example of cleanup that belongs in a finally block rather than after the try...catch, and say why.
Solution
Step 1 — Name a cleanup example: Closing a connection (or a file) that was opened inside the try belongs in finally.
Step 2 — Say why: If the cleanup line were written after the whole try...catch and the catch itself did not handle everything, an escaping error would skip those lines below the block — leaving the connection open. A finally block runs whether or not an error occurred and whether or not it was caught, so the cleanup is guaranteed.
Answer: Example: closing a network connection opened in the try. It belongs in finally because finally runs even if the error handling itself fails, whereas plain lines after the block would be skipped by an uncaught error.
Key Terms
Runtime error -- An error that occurs while a program is running, in code the engine understood well enough to start. It stops execution at that point.
Syntax error -- An error found while the engine reads the code, before anything runs. It cannot be caught, because the program never starts.
try...catch -- A statement that runs risky code and, on a runtime error, abandons the rest of the try block and runs the catch block instead.
Error object -- The value handed to catch, carrying a name (the kind of error) and a message (what specifically went wrong).
ReferenceError -- The error raised when code names something that does not exist.
TypeError -- The error raised when a value exists but is not the kind of thing the code tried to use it as.
throw -- A statement that raises an error deliberately, used when a value breaks the program's rules rather than the language's.
finally -- A block that runs after a try...catch whether or not an error occurred; used for cleanup that must happen either way.
NaN -- "Not a Number", the value Number() produces from text it cannot convert. It does not throw, which is why it is worth checking for.