JavaScript Try Finally vs Return Statement Execution
In JavaScript, the finally block in a
try...catch...finally construct is guaranteed to execute
even if a return statement is encountered inside the
try or catch block. When a return
is triggered in a try block, JavaScript evaluates the
return expression, pauses the return operation, executes the code inside
the finally block, and only then finishes exiting the
function. This article breaks down the exact execution order, explains
how return values are preserved or overwritten, and highlights common
edge cases.
The Execution Order
When a return statement is reached inside a
try block:
- The expression following the
returnkeyword is evaluated. - The evaluated value is saved in a temporary internal location.
- Control immediately transfers to the
finallyblock. - The
finallyblock executes completely. - Control returns to the caller with the saved value (unless altered
by the
finallyblock).
Basic Return Behavior
Consider the following example:
function testExecution() {
try {
console.log("1. Inside try");
return "Result from try";
} finally {
console.log("2. Inside finally");
}
}
const output = testExecution();
console.log("3. Output:", output);Output:
1. Inside try
2. Inside finally
3. Output: Result from try
Even though return appears before the
finally block in the source code, the code inside
finally runs before the function officially returns control
to the calling scope.
Overriding the Return Value
If the finally block itself contains a
return statement, it completely overrides any prior
return statement from the try or
catch blocks. The previously calculated return value is
discarded.
function overrideReturn() {
try {
return "Value from try";
} finally {
return "Value from finally";
}
}
console.log(overrideReturn()); // Output: "Value from finally"The same override occurs if the try block throws an
error. If the finally block returns a value, the thrown
exception is silenced and discarded.
function suppressError() {
try {
throw new Error("Something went wrong");
} finally {
return "Error suppressed";
}
}
console.log(suppressError()); // Output: "Error suppressed"Modifying Return Values (Primitives vs. Objects)
Because the return expression is evaluated before the
finally block runs, the behavior differs between primitive
values and reference types (objects and arrays).
Primitives
Reassigning a primitive variable inside finally does not
change the already-evaluated return value:
function returnPrimitive() {
let count = 1;
try {
return count; // Evaluates to 1 immediately
} finally {
count += 5; // Modifies local variable, but return value is already set
}
}
console.log(returnPrimitive()); // Output: 1Reference Types (Objects/Arrays)
Mutating the properties of an object inside finally
will reflect in the returned result because the reference
points to the same underlying memory:
function returnObject() {
const data = { status: "pending" };
try {
return data; // Evaluates reference to data
} finally {
data.status = "completed"; // Mutates the referenced object
}
}
console.log(returnObject()); // Output: { status: "completed" }Key Rules to Remember
finallyalways runs: Areturnintrydoes not bypass thefinallyblock.- Evaluation order: Expressions in
returnstatements are evaluated before enteringfinally. - Avoid returning from
finally: Returning a value from afinallyblock is generally considered an anti-pattern because it masks earlier returns and silently swallows unhandled exceptions.