skip to content

In a classic non-module script, what does `console.log(typeof foo); var foo = 1; function foo() {} console.log(typeof foo);` print, and why?

level: middleimportance: nice to knowfreq 25%

answer

  1. there is only one binding, not two
  2. setup pass, then execution
  3. function initialisation beats var initialisation
  4. the assignment is what runs later
  5. a bare var with no initialiser assigns nothing

basics

~20 s

It prints "function" and then "number". When the scope is entered, the function declaration wins and puts the function in the shared binding; the var declaration does not overwrite it. Later the assignment foo = 1 runs and replaces the value.

solid answer

~40 s

`var foo` and `function foo` declare the *same* binding in the same scope, so there is only ever one `foo`. During the setup step that runs before any statement, `var` creates the name and would initialise it to `undefined`, but a function declaration for the same name initialises it with the finished function object — and a `var` never clobbers a binding that already exists. So the first `typeof` sees `"function"`. Then execution begins, the statement `foo = 1` runs (the `var` keyword contributes nothing more at run time), and the second `typeof` sees `"number"`. The general rule: at scope entry the function declaration wins; at run time whatever assignment executes last wins.

code

javascript · 15 lines
javascript
function demo() {
  console.log(typeof foo); // "function"
  var foo = 1;
  function foo() {}
  console.log(typeof foo); // "number"
}

function noInitializer() {
  var bar;                  // declaration only, no assignment runs
  function bar() {}
  console.log(typeof bar);  // "function"
}

demo();
noInitializer();

go deeper

for a junior

Recall that a function declaration is already the function when the scope starts, so a typeof before any assignment reports "function", and that a later assignment simply replaces the value.

for a middle

Walk the two phases explicitly: the setup pass where var creates the name and the function declaration initialises it, then execution where only the assignment statement has an effect.

for a senior

Use it to show you read legacy code correctly — explain why duplicate declarations silently pick the last one, why source order between the two lines is irrelevant, and which lint rules catch the accident.

for a principal

Treat it as evidence for the standard you set: names that mean two things are a defect, and the codebase policy plus lint configuration should make a duplicate or mixed declaration impossible to merge.

## One name, one binding The first thing to see is that this snippet does not create two variables. `var foo` and `function foo` are both var-scoped declaration forms, and in the same scope they name the same binding. Declaring a name twice with `var` is legal and harmless; mixing `var` with a function declaration is equally legal — the question is only what value the single binding holds at each moment. ## Step one: scope instantiation Before the first statement of a script or function body executes, the engine sets up the scope's bindings. Walking through it for this code: 1. It sees `var foo` and creates a binding named `foo`. If the binding is new, it is initialised to `undefined`. 2. It sees `function foo() {}`, builds the function object, and initialises `foo` with it — this step deliberately overwrites whatever the `var` left there. The order in the source does not matter for step 2: function declarations are processed after the plain `var` names, and a `var` never re-initialises a binding that already exists. So when the first statement finally runs, `foo` holds the function. ```js console.log(typeof foo); // "function" var foo = 1; function foo() {} console.log(typeof foo); // "number" ``` ## Step two: execution Now the statements run top to bottom. `var foo = 1;` splits into two halves: the declaration part, which already happened during instantiation, and the *assignment* part, which is an ordinary statement executed here. It stores `1` in the binding, replacing the function. The function declaration line does nothing at run time — its entire effect happened at instantiation. That is why moving it above or below `var foo = 1` changes nothing about the output. Swap the two lines and you still get `"function"` then `"number"`. A related detail: a bare `var foo;` with no initialiser is not an assignment at all, so it cannot clobber anything at run time: ```js function f() { var foo; // no assignment happens here function foo() {} return typeof foo; // "function" } ``` Many people expect `"undefined"` from that, reasoning that `var foo;` "resets" the name. It does not; it only ensures the binding exists, and the binding already does. ## Duplicate function declarations The same instantiation step explains what happens with two functions of the same name in one var scope: ```js function pick() { return "first"; } function pick() { return "second"; } console.log(pick()); // "second" ``` Both are processed in source order during instantiation, so the later one overwrites the earlier — and it wins even for calls written *above* both declarations, because the overwrite happened before any statement ran. No error, no warning; the first function is simply unreachable. Linters flag this (`no-redeclare` and `no-func-assign` in ESLint) precisely because a duplicate name is nearly always a merge accident or a copy-paste mistake. Note that the tolerant behaviour comes from these being *var-scoped* declaration forms. Block-scoped forms are stricter: two `let` declarations of the same name in one scope, or a `let` and a `var` colliding, are a `SyntaxError` caught before anything runs. ## Why this shows up in interviews On its face it is a whiteboard trick, but it tests something real: whether you can separate *when a binding is created and initialised* from *when an assignment executes*. Candidates who think "hoisting moves code to the top" get this wrong, because under that mental model the function declaration would have moved above `var foo = 1` and then been overwritten — which is right here by accident, but produces the wrong answer for the first `typeof`. Candidates who think in terms of an instantiation pass followed by ordinary execution get both lines right and can also explain the `var foo;` variant and the duplicate-declaration case. In real code, of course, none of this should appear. A single name meaning both a function and a number is a defect; the value of knowing the rules is being able to read the intent out of code someone else already wrote, and to explain a confusing stack trace in a legacy file.

  • Does the answer change if the function declaration is written before the var statement?
    No — the output is still `"function"` then `"number"`. Both declarations are processed during the scope-setup pass regardless of their order, and the function's initialisation wins there. At run time the only statement with an effect is `foo = 1`, so the second reading is a number either way.
  • What if the code is `var foo;` with no initialiser, alongside `function foo() {}`?
    `typeof foo` stays `"function"` throughout. A `var` with no initialiser contributes nothing at run time — it only guarantees the binding exists, and it already does. Nothing ever overwrites the function object, which surprises people who read `var foo;` as resetting the name to `undefined`.
  • What happens with two function declarations of the same name in one scope?
    The later one wins, silently, and it wins even for calls written above both — the overwrite happens during scope setup, before any statement runs. There is no error because function declarations are var-scoped, unlike `let`, where a duplicate is a `SyntaxError`. Linters flag it since it is almost always a merge mistake.

saying these in an interview costs you the question

  • Says the two declarations create two separate variables
  • Answers "undefined" then "number", ignoring function initialisation
  • Claims mixing var and a function of the same name is a SyntaxError
  • Thinks the source order of the two declarations changes the result
  • Believes a bare var with no initialiser resets the binding to undefined

context