JavaScript hoisting and scope interview questions, with answers
Scope decides which variable a name refers to, and hoisting decides when that variable exists. Together they explain a large share of JavaScript's 'what does this print?' questions: a variable that reads as undefined before its line, a let that throws where a var would not, a function callable above its definition while a function expression is not. The rules are few and exact, and every answer below shows them in code. Every output shown was produced by running the code in Node.js.
The questions start with hoisting and the three ways to declare a variable, then the dead zone, then the scope chain and strict mode. Then take the free JavaScript diagnostic — ten questions across every JavaScript topic in the bank — to see which of these you can explain but not yet predict.
The questions, with answers
1.What is hoisting in JavaScript?
In short: Before any code in a scope runs, JavaScript creates all of the scope's declarations: var variables start as undefined, function declarations are fully defined, and let, const and class exist but cannot be used yet.
Hoisting is a name for what happens when a scope is entered: the engine finds every declaration in it and creates the bindings before running the first line. In effect the declaration moves up and the assignment stays where it is. That is why reading a var before its line gives undefined instead of an error, which is this page's sample, and why a function declaration can be called above the place it is written, as below. let, const and class are hoisted too, but they stay uninitialised until their declaration runs, and touching them earlier throws a ReferenceError; that gap is the temporal dead zone. Nothing physically moves in the source, and interviewers accept either description if you can predict the output.
console.log(add(2, 3)); // 5 function add(a, b) { return a + b; } console.log(total); // undefined var total = 10;
2.What is the difference between var, let and const in JavaScript?
In short: var is function-scoped and can be redeclared; let and const are block-scoped and sit in the temporal dead zone until declared; const also forbids reassignment, though not mutation.
var belongs to the whole function, or becomes a property of the global object at a script's top level, and a block such as an if or a loop body does not contain it, as below. let and const belong to the nearest block, which is why a let loop variable gets a fresh binding on every iteration while a var loop variable is shared by all of them. Redeclaring a let or const in the same scope is a SyntaxError, where var silently allows it. const requires an initial value and forbids reassigning the name, but the value it holds can still change: a const array can be pushed to. Modern code uses const by default, let when a value must change, and var not at all.
if (true) { var a = 1; let b = 2; } console.log(a); // 1 console.log(typeof b); // undefined const xs = [1]; xs.push(2); console.log(xs.length); // 2
3.What is the temporal dead zone in JavaScript?
In short: It is the stretch between entering a scope and reaching a let, const or class declaration, during which the binding exists but any access to it throws a ReferenceError.
The name refers to time, not position. The binding is created when its scope starts, but it is not initialised until the declaration executes, and reading or writing it before then throws. Even typeof, which returns "undefined" for a name that was never declared, throws for a variable in its dead zone, as the last lines below show. The design makes use before declaration a loud error instead of var's silent undefined. A function that refers to a let declared later in the same scope is fine, as long as it is not called before the declaration line has run: the arrow function below is created early but reads the variable only when called, after the let has executed.
console.log(typeof nope); // undefined const later = () => late; let late = "ok"; console.log(later()); // ok console.log(typeof soon); // throws ReferenceError let soon = 1;
4.Are function declarations and function expressions hoisted the same way?
In short: No: a function declaration is hoisted with its body, so it can be called before its line; a function expression is only a value assigned to a variable, hoisted the way that variable is.
function load() {} creates the whole function when the scope is entered. const load = function () {} and const load = () => {} create the function only when that line runs; before it, the name is in the temporal dead zone for const or let, so a call throws a ReferenceError, and holds undefined for var, so a call throws a TypeError saying it is not a function, as below. Function declarations inside blocks are block-scoped in strict mode and follow legacy rules outside it, one more reason to prefer expressions there. Class declarations behave like let: hoisted, but unusable before their line.
try { run(); } catch (e) { console.log(e.name); } // TypeError var run = function () {};
5.What is the scope chain in JavaScript?
In short: Each function or block scope links to the scope it was written inside; a name is looked up in the current scope and then outwards through those links, and a name found nowhere is a ReferenceError.
JavaScript uses lexical scope: where a function is written, not where it is called, decides which outer variables it can see. A lookup starts in the innermost scope and walks outwards, stopping at the first match, so an inner variable shadows an outer one of the same name, as the function below returns "outer" rather than "global". Reaching the global scope without a match throws a ReferenceError when reading. Assigning to an undeclared name is different: in sloppy mode it silently creates a global variable, and in strict mode, which modules and classes use automatically, it throws. The scope chain is also what a closure keeps: a function returned from another still has the link to the scope it was created in.
const x = "global"; function outer() { const x = "outer"; return function inner() { return x; }; } console.log(outer()()); // outer
6.What does "use strict" change about variables and scope?
In short: Strict mode turns silent scope mistakes into errors: assigning an undeclared name throws instead of creating a global, and this in a plain function call is undefined rather than the global object.
A "use strict" directive at the top of a script or a function opts it in; ES modules and class bodies are strict automatically, so modern code usually is. For scope, the important change is that an assignment to an undeclared variable throws a ReferenceError instead of creating a global, which catches the classic typo below. eval also gets its own scope instead of adding variables to its caller's. Strict mode forbids duplicate parameter names and the with statement, makes assignments to read-only properties throw instead of failing silently, and leaves this undefined in a function called without an object. It cannot be switched off inside code that is already strict.
"use strict"; function tally() { totl = 1; } tally(); // throws ReferenceError
7.What is an IIFE in JavaScript, and why was it used?
In short: An immediately invoked function expression is a function defined and called in one step; before let, const and modules, it was the standard way to give code a private scope.
(function () { ... })() wraps code in a function expression and runs it at once. Everything declared inside stays local, so a library could keep its helper variables out of the global scope and return only what it chose to expose, which is the module pattern. The parentheses around the function make the parser read an expression rather than a declaration, which could not be called directly. Today block scope and ES modules cover most of these uses: a block with let and const is enough for a private scope, and every module has its own. IIFEs survive for one-off setup, such as an async arrow function called at once where top-level await is unavailable.
const id = (() => { let next = 100; return () => next++; })(); id(); console.log(id()); // 101
How the diagnostic asks it
One question from the JavaScript bank, exactly as a sitting would show it. The bank has 4 on scope & hoisting and 30 across JavaScript.
What does this code print?
console.log(x); var x = 5;
- 15
- 2ReferenceError is thrown
- 3null
- 4undefinedcorrect
A var declaration is hoisted to the top of its function or script and initialised to undefined, but the assignment = 5 stays where it is, so the log runs after the declaration and before the assignment and prints undefined. 5 assumes the assignment is hoisted too. ReferenceError is what let or const would give here, because their bindings stay uninitialised until the declaration runs. null is never the default for an unassigned variable in JavaScript.
Measure it
Reading answers tells you what’s true. A diagnostic tells you what you get wrong.
10 JavaScript questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.