Error Handling
try/catch/finally, throwing, the Error object and its subtypes, custom errors, strict mode, and asynchronous error handling.
12 questions
JuniorTheoryVery commonWhat properties does an Error object have, and what built-in subtypes exist?
What properties does an Error object have, and what built-in subtypes exist?
An Error carries name (the type, e.g. 'Error'), message (the human-readable text passed to the constructor), and a non-standard but widely supported stack string showing the call stack. Built-in subtypes inherit from Error and set their own name: TypeError, RangeError, SyntaxError, ReferenceError, plus URIError and EvalError.
Common mistakes
- ✗Treating
stackas a standardized property guaranteed on every engine - ✗Not knowing built-in subtypes inherit from
Error, soinstanceof Erroristrue - ✗Confusing
name(the type) withmessage(the human-readable text)
Follow-up questions
- →What kind of mistake throws a
ReferenceErrorversus aTypeError? - →Why is a
SyntaxErrorfrom bad source code usually impossible tocatch?
JuniorTheoryVery commonWhat do try, catch, and finally each do in a JavaScript statement?
What do try, catch, and finally each do in a JavaScript statement?
try wraps code that might throw. If it throws, control jumps to catch, which receives the thrown value as a binding. finally runs afterward whether or not an error occurred, even after a return, so it is used for cleanup. Since ES2019 the catch binding is optional: catch {} is valid when you ignore the error.
Common mistakes
- ✗Thinking
finallyis skipped when thetryblock returns or throws - ✗Believing the catch binding is required, not knowing
catch {}is valid since ES2019 - ✗Assuming
catchruns even on a successfultrywith no error
Follow-up questions
- →If both the
tryand thefinallyblock return a value, which return wins? - →When would you use
try/finallywith nocatchat all?
JuniorTheoryCommonWhat is 'use strict', and what does enabling strict mode do?
What is 'use strict', and what does enabling strict mode do?
'use strict' is a string directive at the top of a script or function that opts that scope into strict mode. It makes the engine reject sloppy patterns: assigning to an undeclared variable throws instead of creating a global, duplicate parameter names are errors, and this is undefined in a plain function call. ES modules and class bodies are always strict, so the directive is needed only in legacy scripts.
Common mistakes
- ✗Thinking strict mode adds static type checking to JavaScript
- ✗Not knowing ES modules and class bodies are strict by default
- ✗Believing the directive applies globally rather than per script or function
Follow-up questions
- →Why must the
'use strict'directive be the very first statement in its scope? - →How does strict mode change the value of
thisin a standalone function call?
MiddleTheoryCommonHow do you handle errors from promises and async/await code?
How do you handle errors from promises and async/await code?
A rejected promise can be handled with .catch(handler) chained after .then. With async/await, wrap the await in try/catch, since a rejection turns into a thrown error at the await point. If neither is present the rejection is unhandled: the browser fires a global unhandledrejection event, and Node logs a warning. try/catch cannot catch a rejection you never await.
Common mistakes
- ✗Wrapping a promise in
try/catchwithoutawait, so the rejection escapes - ✗Forgetting
.catchon a promise chain, leaving the rejection unhandled - ✗Not knowing the browser fires
unhandledrejectionfor missed rejections
Follow-up questions
- →Why can a synchronous
try/catchnot catch a rejection from an un-awaited promise? - →What does the
unhandledrejectionevent let you do, and can you prevent the default?
JuniorTheoryOccasionalWhat does the throw statement do, and what kinds of values can you throw?
What does the throw statement do, and what kinds of values can you throw?
throw expr immediately stops the current function and starts unwinding the stack looking for a catch. You can throw ANY value — a string, number, or object — but you should throw an Error instance, because only those carry a message and stack for diagnostics. Inside a catch, throw err rethrows the same error to let an outer handler deal with it.
Common mistakes
- ✗Thinking only
Errorinstances can be thrown, when any value is allowed - ✗Believing
throwreturns to the caller instead of unwinding the stack - ✗Assuming rethrowing with
throw errrebuilds the stack from the rethrow site
Follow-up questions
- →Why is throwing a plain string instead of an
Errordiscouraged in practice? - →How does rethrowing inside a
catchpreserve the original stack trace?
MiddleTheoryOccasionalHow do you define a custom error class, and what does the cause option add?
How do you define a custom error class, and what does the cause option add?
Write class MyError extends Error and call super(message) in its constructor, then set this.name = 'MyError' so the type shows correctly. Instances pass both instanceof MyError and instanceof Error, letting handlers branch by type. The ES2022 cause option — new Error('msg', { cause: original }) — attaches the underlying error as err.cause, preserving the original when you wrap and rethrow.
Common mistakes
- ✗Forgetting
super(message), so the basemessageand stack are not set up - ✗Not setting
this.name, leaving the error reporting as a genericError - ✗Thinking
causeoverwritesmessageinstead of attaching the original error
Follow-up questions
- →Why should a custom error set
this.namerather than rely on the class name? - →How does
err.causehelp when wrapping a low-level error in a domain-specific one?
MiddleTheoryOccasionalHow does an uncaught error propagate up the call stack to be handled?
How does an uncaught error propagate up the call stack to be handled?
When a function throws and has no local catch, the engine unwinds the stack: it abandons that function and each caller in turn, looking for the nearest enclosing try/catch. The first matching catch up the chain handles it; code after each abandoned call is skipped. If no handler is found at all, the error reaches the top and becomes an uncaught exception. So you catch where you can meaningfully recover, not necessarily at the throw site.
Common mistakes
- ✗Thinking an error propagates into callees rather than up to callers
- ✗Believing propagation stops at the immediate caller instead of climbing
- ✗Assuming an error lands in the outermost
try, not the nearest enclosing one
Follow-up questions
- →Why is catching an error at the throw site often worse than letting it propagate?
- →What happens to code that follows an abandoned function call during unwinding?
MiddleTheoryOccasionalWhen does finally run, and what happens if it contains a return or throw?
When does finally run, and what happens if it contains a return or throw?
finally runs after the try/catch completes, on every path — normal exit, a return, or an uncaught throw — which makes it ideal for cleanup. But a return or throw inside finally overrides whatever the try or catch was about to return or throw: the pending value is discarded and the finally result wins. So putting control flow in finally can silently swallow errors.
Common mistakes
- ✗Thinking a
returnintryskips thefinallyblock - ✗Not knowing a
returninfinallydiscards thetry/catchreturn value - ✗Believing a
throwinfinallyis caught by the same statement'scatch
Follow-up questions
- →Why is putting a
returninsidefinallyconsidered a dangerous anti-pattern? - →If
trythrows andfinallyalso throws, which error propagates to the caller?
MiddleTheoryOccasionalWhat silent failures does strict mode turn into thrown errors?
What silent failures does strict mode turn into thrown errors?
Strict mode promotes several quiet sloppy-mode behaviours into errors: assigning to an undeclared variable throws a ReferenceError instead of creating an implicit global; writing to a read-only or non-writable property throws a TypeError rather than failing silently; and delete of a non-configurable property throws. It also sets this to undefined in a plain function call, so accidental globals via this are caught.
Common mistakes
- ✗Thinking strict mode only warns rather than throwing on these failures
- ✗Assuming
thisis still the global object in a plain strict-mode call - ✗Believing an undeclared assignment still creates an implicit global
Follow-up questions
- →Why is the strict
this-is-undefinedrule a safeguard against accidental globals? - →What
TypeErrordoes writing to a frozen object's property throw under strict mode?
SeniorTheoryOccasionalWhy does a throw inside setTimeout escape a surrounding try/catch?
Why does a throw inside setTimeout escape a surrounding try/catch?
A try/catch only guards the synchronous call stack active when it runs. The setTimeout callback fires later on a fresh stack via the event loop, long after the try block returned, so its throw has no enclosing handler and becomes uncaught. Promises fix this: a throw inside a then/async body rejects the promise, catchable with .catch or await in try/catch. With Promise.all, the first rejection rejects the whole combinator immediately.
Common mistakes
- ✗Thinking a
try/catchguards callbacks that run later on a new stack - ✗Believing a throw inside an
asyncbody orthenis swallowed, not a rejection - ✗Assuming
Promise.allwaits for all rejections instead of rejecting on the first
Follow-up questions
- →How can you handle an error thrown inside a
setTimeoutcallback in practice? - →How does
Promise.allSettleddiffer fromPromise.allin handling rejections?
SeniorTheoryOccasionalHow do error.cause chains, AggregateError, and global error handlers fit together?
How do error.cause chains, AggregateError, and global error handlers fit together?
error.cause lets you wrap a low-level error in a domain-specific one while keeping the original — handlers can walk the cause chain to find the root failure. AggregateError bundles several errors into one (its .errors array), as Promise.any does when every input rejects. As a last resort, global handlers — window.onerror and the unhandledrejection event in browsers, or process.on('uncaughtException') in Node — catch what escaped all try/catch.
Common mistakes
- ✗Thinking
causeoverwrites the message rather than linking the original error - ✗Believing
AggregateErrorkeeps one error, not an.errorsarray of all of them - ✗Assuming a global handler pre-empts a local
try/catchinstead of being last resort
Follow-up questions
- →Which promise combinator produces an
AggregateError, and under what condition? - →Why is
window.onerrora monitoring tool rather than a real recovery mechanism?
SeniorTheoryOccasionalWhat are the full effects of strict mode, and where is it always on?
What are the full effects of strict mode, and where is it always on?
Strict mode forbids implicit globals, throws on writes to read-only or non-existent-target properties, bans duplicate parameter names and with, makes this undefined in plain calls, and reserves words like implements and interface. Crucially, ES modules and class bodies are strict automatically — the directive is never needed there. So a frozen object's property assignment throws a TypeError inside a module, whereas sloppy scripts would fail silently.
Common mistakes
- ✗Thinking ES modules and class bodies need an explicit
'use strict' - ✗Believing a write to a frozen property fails silently rather than throwing
- ✗Assuming strict mode only warns instead of throwing real
TypeErrors
Follow-up questions
- →Why does assigning to a property of a frozen object throw only under strict mode?
- →How does strict mode being default in modules change how you write library code?