skip to content

In JavaScript, given `const f = function fact(n) { return n < 2 ? 1 : n * fact(n - 1); };`, where is the name `fact` visible, and what do you gain by naming a function expression instead of leaving it anonymous?

level: middleimportance: should knowfreq 35%

answer

  1. one identifier, two different scopes
  2. a private binding around the function body
  3. typeof outside gives undefined
  4. immutable inside the body
  5. what shows up in a stack trace

basics

~20 s

The name of a named function expression is visible only inside that function's own body, not in the surrounding scope. It gives the function a reliable way to call itself and puts a real name in stack traces and debugger frames.

solid answer

~40 s

A named function expression creates an extra, invisible scope around the function that contains one binding: the function's own name. So `fact` can be used inside the body, but outside the function only `f` exists — `typeof fact` is `"undefined"` there. That inner binding is immutable: assigning to `fact` inside the body throws a `TypeError` in strict mode and is silently ignored in sloppy mode. The payoff is twofold. Recursion keeps working even if the outer variable is reassigned or the function is passed around under a different name, and the name shows up as `f.name` and in stack traces and debugger frames instead of an anonymous entry, which makes production stack traces readable.

code

javascript · 13 lines
javascript
let g = function (n) { return n < 2 ? 1 : n * g(n - 1); };
const copy = g;
g = null;
try {
  copy(5); // recursion goes through the outer name, which is now null
} catch (e) {
  console.log(e.constructor.name); // "TypeError"
}

let safe = function fact(n) { return n < 2 ? 1 : n * fact(n - 1); };
const safeCopy = safe;
safe = null;
console.log(safeCopy(5)); // 120 - self-name still resolves

go deeper

for a junior

Recall that a function expression may carry its own name, that this name works inside the function but is not available to the code around it, and that it shows up in stack traces.

for a middle

Explain the extra environment the engine creates for the self-name, why it is immutable, and how it makes recursion survive reassignment of the outer variable.

for a senior

Argue the operational case: readable frames in minified production stack traces and profiler output, and knowing exactly where automatic name inference stops — callbacks passed directly into a call.

for a principal

Set the convention across the codebase, weighing consistent named callbacks against the noise they add, and tie it to how the team's source maps and error-reporting pipeline actually render frames.

## Two names, two scopes `const f = function fact(n) { ... }` has two identifiers, and they live in different places. - `f` is an ordinary `const` binding in the surrounding scope. That is the name callers use. - `fact` is the function expression's own name. When the expression is evaluated, the engine creates a small extra environment whose only entry is `fact`, bound to the function object, and makes the function's body close over it. That extra environment sits between the function body and the enclosing scope, so the body can see `fact`, while nothing outside can: ```js const f = function fact(n) { return n < 2 ? 1 : n * fact(n - 1); }; console.log(f(5)); // 120 console.log(typeof fact); // "undefined" - not a binding out here ``` `typeof` on a name that was never declared yields `"undefined"` rather than throwing, which is why that last line is safe. A bare `fact` on its own would throw `ReferenceError: fact is not defined`. This is exactly what separates a named function expression from a function declaration. A declaration `function fact(n) {}` puts `fact` into the enclosing scope for everyone to use. The expression form keeps the name private to the function. ## The inner binding is immutable The self-name binding is created as an immutable binding, so the body cannot repoint it: ```js const f = function fact() { "use strict"; fact = null; // TypeError: Assignment to constant variable }; ``` In sloppy-mode code the same assignment is simply ignored rather than throwing — one more reason to keep strict mode on, since a silent no-op is harder to spot than an exception. Parameters and inner `var`/`let` declarations with the same name shadow it normally; the immutability applies only to the self-name binding itself. ## Why bother naming it **Robust self-reference.** Recursing through the outer variable couples the function to whatever name currently holds it: ```js let g = function (n) { return n < 2 ? 1 : n * g(n - 1); }; const h = g; g = null; h(5); // TypeError: g is not a function ``` With the self-name, the same shuffle is harmless because the recursive call resolves through the private binding, not through whatever the outer variable now holds. This matters most for functions that are stored in objects, passed as callbacks, or re-exported under another name. **Readable stack traces and debugger frames.** Engines derive the displayed frame name from the function's `name` property. A named function expression sets `name` to the given name, so a production stack trace shows `at fact (bundle.js:...)` rather than `at <anonymous>`. On a minified bundle that difference is often the only clue about which callback threw. ## The name-inference rule you still need to know Modern JavaScript infers a `name` for many anonymous functions from what they are assigned to, so the anonymous form is not always nameless: ```js const greet = function () {}; console.log(greet.name); // "greet" const shout = () => {}; console.log(shout.name); // "shout" console.log([].map(function () {}).name); // undefined - no array element to inspect ``` Inference works for direct assignment to a variable, a property in an object literal, or a default value — anywhere the target's name is statically obvious. It does **not** apply when the function is passed straight into a call, which is precisely where anonymous callbacks are most common and where a stack trace matters most. An explicit name always wins: ```js emitter.on("data", function onData(chunk) { /* named in stack traces */ }); ``` Also note that inference never creates a scope binding — `greet.name` being `"greet"` is just a string property; there is no `greet` identifier visible inside the anonymous function's body the way `fact` is inside the named one. ## Practical guidance Use a named function expression whenever the function recurses, whenever you register a callback you may later want to identify in a trace or a profiler, and whenever the value may be copied to another variable. Keep the two names identical unless you have a reason not to: `const fact = function fact(n) {...}` reads fine and eliminates any confusion about which name callers should use.

  • What happens if code inside the body assigns to the function expression's own name?
    Nothing useful. The self-name is an immutable binding, so in strict mode the assignment throws a `TypeError`, and in sloppy mode it is silently ignored — the name keeps pointing at the function. Parameters or inner declarations that reuse the name shadow it instead, which is legal but confusing enough to avoid.
  • If name inference already gives an anonymous function a name property, when is the explicit name still worth writing?
    Whenever inference does not apply or the name must be usable as an identifier. Inference works for direct assignment to a variable, object-literal property, or default value, but not for a function passed straight into a call — the common callback case. And inference only sets the `name` property; it never creates a binding the body can call itself through.
  • Does naming a function expression make it callable before its assignment line?
    No. The name is created inside the function's own scope when the expression is evaluated, so it exists only while the function runs. Nothing is added to the enclosing scope early, and the outer binding behaves exactly as it would for an anonymous expression — unusable until its assignment executes.

saying these in an interview costs you the question

  • Thinks the expression's name is added to the enclosing scope
  • Says a named function expression is the same as a declaration
  • Claims naming it makes the function callable before its assignment
  • Believes anonymous functions always have an empty name property
  • Assumes you can reassign the inner self-name to swap implementations

context