Why a function declaration after a return statement still shadows your outer variable — and how this "dead code" hoisting trap silently breaks state updates in production JavaScript.
This post explains a quiz originally shared as a LinkedIn poll.
🔹 The Question
var x = 1;
function bar() {
x = 10;
console.log(x);
return;
function x() {}
}
bar();
console.log(x);
Hint: Before any code inside a function runs, the engine has already processed all declarations in the function body — including ones that appear after a return statement.
Follow me for JavaScript puzzles and weekly curations of developer talks & insights at Talk::Overflow: https://talkoverflow.substack.com/
🔹 Solution
Correct answer: B) 10, 1
The first console.log prints 10. The second prints 1.
🧠 How this works
This quiz exploits the fact that function declarations are hoisted to the top of their enclosing function scope — even when they appear after a return statement, where they can never be reached at runtime.
When the engine enters bar(), it performs declaration hoisting before executing any code. It finds function x() {} at the bottom of the function body and creates a local binding x initialized to that function. This local x completely shadows the outer var x = 1.
At runtime, the function effectively becomes:
function bar() {
var x = function x() {}; // hoisted function declaration
x = 10; // reassigns local x to 10
console.log(x); // 10
return;
}
The x = 10 assignment targets the local x, not the outer one. The outer x remains 1 throughout.
This pattern commonly surfaces when developers add utility functions inside other functions during refactoring, not realizing those function names may collide with outer variables.
🔍 Line-by-line explanation
var x = 1;— Declaresxin the outer (global/module) scope and initializes it to1.function bar() {— The engine parses the entire body ofbarbefore executing it. It encountersfunction x() {}and hoists it — creating a local bindingxinsidebar, initialized to the function object. This localxshadows the outerxfor the entire duration ofbar.x = 10;— This assignment targets the localx(the hoisted function declaration). The localxis now10. The outerxis untouched.console.log(x);— Prints10(the localx).return;— Exitsbar. Everything below this line is unreachable at runtime.function x() {}— This is a function declaration. Its positional placement afterreturnis irrelevant — the declaration was already hoisted during the parsing phase. It has no runtime effect here.bar();— Callsbar, which logs10as described above.console.log(x);— Prints the outerx, which is still1. The assignment insidebarnever touched it.
The non-obvious part: Most developers read x = 10 and assume it refers to the outer x because they don't see any local declaration of x before the return. The function x() {} after return looks dead — but its declaration is very much alive due to hoisting.
🔹 Key Takeaways
Function declarations are hoisted regardless of reachability. A
functiondeclaration afterreturn, inside a never-enteredifbranch, or at the end of a thousand-line function is still hoisted to the top of its enclosing scope.Hoisted function declarations create local bindings that shadow outer variables. Unlike a simple
x = 10assignment (which would target the outer scope), the presence offunction x() {}anywhere in the function body creates a localxthat captures all references toxwithin that function."Dead code" can have live side effects. In JavaScript, unreachable function declarations still participate in scope creation. Never assume code after
returnis safe to ignore during code review.Use
const/letand arrow functions to avoid this class of bugs.const x = () => {}is not hoisted and will throw aReferenceErrorif accessed before its declaration, making the bug immediately visible instead of silent.Linting rules like
no-unreachableandno-shadowcatch this pattern. Enable them to flag function declarations afterreturnstatements and variable name collisions across scopes.
Top comments (0)