Is `try { ... } finally { ... }` with no `catch` clause legal JavaScript, and what does that shape express that a full try/catch/finally does not?
answer
- catch is not mandatory
- cleanup without claiming to handle
- the error keeps propagating afterwards
- try with neither clause is a SyntaxError
- catch with no binding since ES2019
basics
~20 sYes, it is legal: a try statement needs a catch clause, a finally clause, or both. try/finally with no catch says cleanup must happen but this scope is not the right place to handle the error, so the error runs the cleanup and then keeps propagating.
solid answer
~50 sIt is legal - the grammar requires *at least one* of `catch` and `finally`, so `try { } catch { }`, `try { } finally { }` and `try { } catch { } finally { }` all parse, while `try { }` alone is a SyntaxError. The two-clause `try/finally` form is the most honest thing to write when a function acquires something it must release but has no idea how to recover from a failure: the `finally` block releases the resource, then the error continues unchanged to a caller that does know. The alternative people reach for - catching, cleaning up and rethrowing - is worse, because it is easy to rethrow the wrong thing or accidentally absorb the error. Since ES2019 you can also write `catch { }` with no parameter, the optional catch binding, for the case where you genuinely do not need the thrown value.
code
javascript · 19 linesfunction withLock(lock, work) {
lock.acquire();
try {
return work();
} finally {
lock.release();
}
}
function isValidJson(text) {
try {
JSON.parse(text);
return true;
} catch {
return false;
}
}
console.log(isValidJson('{ bad json')); // falsego deeper
Know that catch is optional: try/finally is valid, the cleanup runs, and the error still travels on to the caller. A try with neither clause is a syntax error.
Explain what the shape communicates - this scope releases, some caller recovers - and why catch-cleanup-rethrow duplicates cleanup, misses return and break exits, and invites accidental absorption.
Apply it to real resources under failure: locks, in-flight counters, spinners, handles. Be ready to say where the handling boundary belongs in a call chain and why cleanup should sit far below it.
Set the convention across a codebase - cleanup local, handling at a small number of deliberate boundaries - and judge when newer explicit resource management syntax is portable enough to replace hand-written pairs.
## Three legal shapes A `try` statement must be followed by a `catch` clause, a `finally` clause, or both: ```js try { risky(); } catch (err) { handle(err); } // handle, no cleanup try { risky(); } finally { cleanup(); } // cleanup, no handling try { risky(); } catch (err) { handle(err); } finally { cleanup(); } ``` `try { risky(); }` on its own is a SyntaxError. The parser refuses a try block that promises nothing. ## What try/finally says The two-clause `try/finally` form encodes a specific and common situation: *this scope acquired something and must release it, but it does not know how to recover from failures here.* Releasing is a local responsibility; recovering is the caller's. ```js function withLock(lock, work) { lock.acquire(); try { return work(); } finally { lock.release(); } } ``` If `work()` throws, the lock is released and the error travels on to whoever called `withLock`. The function has done its whole job - it never pretended to understand the failure. This is the shape to reach for around spinners and loading flags, in-flight counters, temporary state you toggled, timers you started, and any handle you opened. ## Why catch-clean-rethrow is worse The usual alternative looks equivalent and is not: ```js try { return work(); } catch (err) { lock.release(); throw err; // easy to get subtly wrong } lock.release(); // and now the success path needs its own copy ``` Three problems. The cleanup is duplicated across the success and failure paths, so the two drift apart. The `catch` clause is now a place where somebody will later add logic that quietly absorbs the error. And a `return`, `break` or `continue` elsewhere in the try block skips the trailing cleanup entirely, while `finally` would have covered it. The `try/finally` version has one cleanup site that covers every exit. ## Optional catch binding When you *do* handle the error but never look at it, ES2019 lets you omit the binding: ```js function isValidJson(text) { try { JSON.parse(text); return true; } catch { return false; } } ``` Before ES2019 you had to write `catch (err)` and leave `err` unused, which linters then flagged. `catch { }` is not a different kind of catch - it still catches everything thrown, whatever the value - it simply declares that this handler does not need the value. Reach for it only when the *reason* really is irrelevant, as in a probe like the one above; using it to hide an error you should be logging is the classic misuse. ## Choosing between the shapes A quick decision procedure: - Can this scope actually do something useful about the failure - retry, fall back, translate it for its own caller? If yes, you want a `catch`. - Does this scope hold something that must be released regardless of outcome? If yes, you want a `finally`. - Both can be true, and then you write all three clauses. Neither being true means you should not be writing a try statement here at all. The answer people get wrong is the third bullet's inverse: adding a `catch` that only logs and rethrows. That is a `try/finally` with extra noise and an extra opportunity to swallow something. ## A note on newer syntax Explicit resource management - `using` declarations backed by a `Symbol.dispose` method, plus `DisposableStack` - is a recent addition to the language that automates the acquire/release pairing this pattern expresses by hand. Engine support is still arriving, so hand-written `try/finally` remains the portable way to express it and is what interviewers expect you to reach for.
- Why prefer try/finally over catching, cleaning up and rethrowing?Because the rethrow version duplicates cleanup across the success and failure paths, invites someone to later absorb the error inside that catch clause, and misses exits via return, break or continue in the try block. A finally block is one cleanup site that covers every exit, and it never touches the error at all.
- When is the optional catch binding the right choice rather than a smell?When the identity of the error genuinely carries no information for this code - a validity probe such as parsing a string to test whether it is JSON, or a feature-detection attempt. If you would want the message in a log or a report, take the binding. Omitting it to avoid dealing with a real failure is the classic misuse.
- Does a try/finally with no catch stop the error from reaching the caller?No. Only a catch clause handles an error. With just a finally clause, the cleanup runs while the error is unwinding through that frame and the error then continues outward unchanged - same value, same stack - to whichever caller has a handler, or to the top level.
saying these in an interview costs you the question
- Believes every try statement requires a catch clause
- Thinks a finally block quietly handles or absorbs the error
- Writes catch, cleanup, rethrow instead of try/finally
- Uses catch with no binding to hide failures worth logging
- Claims try alone with neither clause is valid syntax