In JavaScript, why can you call a function written as `function greet() {}` from a line above its definition, while calling a function stored by `var sayHi = function () {}` above that assignment throws a TypeError?
answer
- one is a binding form, one produces a value
- what happens when the scope is entered
- the variable exists but holds undefined
- TypeError versus ReferenceError versus works
- arrows have no declaration form
basics
~20 sA function declaration is created whole, name and body together, when the scope is entered, so it is callable anywhere in that scope. A function expression only becomes a function when its assignment line runs; until then the variable holds undefined.
solid answer
~40 sThe `function` keyword in statement position is a declaration: when the engine enters the script, module, or function body, it creates the binding and initialises it with the finished function object before any statement runs. So `greet()` above the declaration works. In `var sayHi = function () {}` the `function` keyword sits where a value is expected, so it is an expression. Only the `var` binding is created up front, initialised to `undefined`; the function object is produced when the assignment executes. Calling `sayHi()` earlier is calling `undefined`, which is a `TypeError: sayHi is not a function`. Arrow functions are always expressions, so they behave the same way — and with `const`/`let` you get a `ReferenceError` instead, because the binding exists but is uninitialised.
code
javascript · 16 linesgreet(); // "hi"
try {
sayHi(); // TypeError: sayHi is not a function
} catch (e) {
console.log(e.constructor.name, e.message);
}
function greet() {
console.log("hi");
}
var sayHi = function () {
console.log("hi");
};
sayHi(); // "hi" - the assignment has now rungo deeper
Be able to state plainly that a function declaration can be called from anywhere in its scope while a function expression cannot be used before its assignment line, and that the early call fails because the variable still holds undefined.
Explain the mechanism: when a scope is entered, var names are initialised to undefined and function declaration names are initialised with the finished function, so the difference is about when the value exists, not about code being moved.
Show you can diagnose it from the error text alone and name the refactor that causes it — turning a declaration into a const arrow while something still calls it during module evaluation. Say which call sites are safe and which are not.
Own the convention: decide whether the codebase favours declarations for order-independence or expressions for explicit definition-before-use, and be able to justify the choice by what it prevents at load time rather than by taste.
## Declaration or expression is decided by position The same `function` keyword produces two different things depending on where it appears. In statement position it is a **function declaration** — a binding form, like `var` or `let`, whose job is to introduce a name. Anywhere a value is expected (right of `=`, after `return`, inside an argument list, inside parentheses) it is a **function expression**, which evaluates to a function object that something else has to store. Arrow functions such as `() => {}` have no statement form at all: they are expressions, always. ```js function a() {} // declaration const b = function () {}; // expression, stored in b const c = () => {}; // expression, stored in c setTimeout(function () {}, 0); // expression, passed as an argument ``` ## What the engine does when it enters a scope Before the first statement of a script, module, or function body runs, the engine walks that code and creates the bindings the scope will need. The specification calls this step declaration instantiation. Two of its rules explain everything here: - every `var` name is created and initialised to `undefined`; - every **function declaration** name is created and initialised with an already-built function object. That second rule is the answer. `greet` is not merely declared early — it already *is* the function before line one executes. "Hoisting" is a nickname for this step; no code is physically moved, and reading the source top-to-bottom is still exactly what the engine does at run time. A function expression gets none of this treatment. Its right-hand side is ordinary code that runs only when control reaches it. ```js greet(); // "hi" sayHi(); // TypeError: sayHi is not a function function greet() { console.log("hi"); } var sayHi = function () { console.log("hi"); }; ``` Before the assignment, `sayHi` holds `undefined`, and calling `undefined` is a `TypeError`. Note the wording: the variable exists, so this is not a "not defined" error. ## The error message tells you which form you used Swap `var` for `const` or `let` and the failure changes: ```js sayHi(); // ReferenceError: Cannot access 'sayHi' before initialization const sayHi = () => console.log("hi"); ``` A `let`/`const` binding is created at scope entry but deliberately left uninitialised, so reading it early throws instead of yielding `undefined`. So three outcomes distinguish the three forms: the call succeeds (declaration), you get `TypeError: x is not a function` (expression held in `var`), or you get `ReferenceError: Cannot access 'x' before initialization` (expression held in `let`/`const`). A fourth message, `ReferenceError: x is not defined`, means no binding exists in scope at all — a typo or a missing import, not a hoisting question. ## Why the distinction shows up in real code The practical payoff is ordering freedom. Because declarations are ready before anything runs, you can put the entry point at the top of a file and the helpers below it, and mutually recursive functions can refer to each other without arranging them in a particular order: ```js function isEven(n) { return n === 0 ? true : isOdd(n - 1); } function isOdd(n) { return n === 0 ? false : isEven(n - 1); } ``` With `const` arrow helpers that same pair still works, because by the time either function is *called* both assignments have run. That is the key nuance: only calls that happen **during** the top-to-bottom evaluation of the file are affected. A helper referenced from inside another function body, an event handler, or a callback is looked up when that code eventually runs, long after the assignments completed. The bug therefore appears when a module does work at load time — registering a handler, building a lookup table, invoking an initialiser — with a helper defined further down as an expression. Converting a declaration to a `const` arrow during a refactor is a classic way to introduce it, because the file still looks fine and only the top-level call path breaks. ## Choosing between them Declarations buy order-independence; expressions buy explicitness — the name is a normal `const` binding, cannot be redeclared, and cannot be called before the line that defines it, which some teams consider a feature. Neither choice affects the function's behaviour once it is running: the only difference is when the name becomes usable.
- What error do you get instead if the function expression is assigned to a const, and why is it different?You get `ReferenceError: Cannot access 'x' before initialization`. A `const` or `let` binding is created when the scope is entered but left uninitialised until its declaration runs, so reading it early throws rather than yielding `undefined`. With `var` the binding is initialised to `undefined`, so the read succeeds and only the *call* fails with a `TypeError`.
- Does the engine literally move function declarations to the top of the file?No. Nothing in the source is rewritten or reordered. Before executing a scope the engine creates its bindings, and a function declaration's binding is initialised with the finished function object at that moment. "Hoisting" describes that initialisation order, not a code transformation, which is why the observable behaviour is exactly as if the name already had its value.
- Why does a helper defined as a const arrow at the bottom of a module usually still work?Because most calls happen after the whole file has evaluated. If the helper is only referenced from inside another function, a callback, or an event handler, that lookup happens when the callback later runs, by which time the assignment has completed. The failure appears only when something at module top level calls the helper during load.
saying these in an interview costs you the question
- Says the engine physically moves declarations to the top of the file
- Claims function expressions are hoisted just like declarations
- Expects ReferenceError when calling a var-held function expression early
- Believes arrow functions are hoisted and callable before their line
- Says the variable does not exist yet, so nothing is defined