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:

  1. The expression following the return keyword is evaluated.
  2. The evaluated value is saved in a temporary internal location.
  3. Control immediately transfers to the finally block.
  4. The finally block executes completely.
  5. Control returns to the caller with the saved value (unless altered by the finally block).

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: 1

Reference 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