TWISTEdBRACKETS

Variable and function hoisting in JavaScript

Published

6 Dec 2022

If you have ever called a function before it appears in a file, or logged a var before its declaration line, you have already seen hoisting in action. The code did not move anywhere. JavaScript simply registered the declaration earlier than you expected.

Press Run on the examples below to execute each snippet and see the terminal output for yourself.

What hoisting actually is

JavaScript runs in two broad phases.

During the creation phase, the engine walks the current scope and registers every declaration. var variables are created and set to undefined. Function declarations are stored with their full definition. let and const are registered too, but they sit in the temporal dead zone until their declaration line runs, which means you cannot read them before that point.

During the execution phase, the engine runs your code top to bottom. Assignments, function calls, and console.log all happen here.

Hoisting is not the engine physically moving lines of code. It is the gap between when a declaration enters memory and when the rest of the line runs. That gap is what makes this work:

hoisting_var.js
a = "Set the value";console.log(a);var a;
ConsolePress “Run” to execute this script.
$ node ./hoisting_var.js

The assignment and console.log happen during execution, but the var a declaration was already registered at the top of the scope with a value of undefined before the first line ran. By the time console.log(a) executes, the assignment has already given a its value.

Compare that with the more familiar order:

hoisting_var_order.js
var a;a = "Set the value";console.log(a);
ConsolePress “Run” to execute this script.
$ node ./hoisting_var_order.js

Same output, same underlying behaviour.

Variable hoisting

The keyword you choose changes what gets hoisted and when you are allowed to read the binding.

var is hoisted and initialised to undefined

The first example above shows the classic pattern: the declaration is hoisted, the assignment stays where you wrote it, and the log runs after the value has been set.

let and const are hoisted but not initialised

let and const declarations are registered during the creation phase, but the engine throws if you access them before the declaration line executes.

hoisting_let.js
a = "Not hoisted";let a;console.log(a);
ConsolePress “Run” to execute this script.
$ node ./hoisting_let.js

That is the temporal dead zone: the binding exists, yet reading or assigning to it before the declaration is a ReferenceError, not undefined.

The same rule applies to const:

hoisting_const.js
console.log(message);const message = "Ready";
ConsolePress “Run” to execute this script.
$ node ./hoisting_const.js

There is no "helpful" undefined to fall back on. The variable simply is not readable yet.

Hoisting scope

Hoisting respects scope. A var declared inside a function is hoisted to the top of that function, not the whole file.

hoisting_scope.js
function functionScope() {  a = "Scope hoisting";  console.log(a);  var a;}
functionScope();console.log(a);
ConsolePress “Run” to execute this script.
$ node ./hoisting_scope.js

Inside functionScope, the hoisted var a makes the assignment and log work. Outside the function, a was never declared in the outer scope, so the final console.log(a) throws a ReferenceError.

If you need a value to survive outside a function, declare it in that outer scope first.

Function hoisting

Function declarations are hoisted with their full body, which is why this works:

hoisting_function.js
console.log(testFunction());
function testFunction() {  return "Function output";}
ConsolePress “Run” to execute this script.
$ node ./hoisting_function.js

The engine registered testFunction during the creation phase, complete with its return value, before console.log ran.

Function expressions behave like other values. Only the declaration itself is hoisted, not the function value, and with let or const the binding is unusable until its line runs:

hoisting_expression.js
console.log(functionExpression());
let functionExpression = function () {  return "Function output";};
ConsolePress “Run” to execute this script.
$ node ./hoisting_expression.js

If you need to call something before its definition line, use a function declaration. If you are assigning a function to a variable, define it before you call it.

Practical advice

Hoisting is valid JavaScript, but it is easy to misread, especially when var, nested functions, and conditionals mix together.

A few habits keep the behaviour predictable:

  • Prefer let and const over var so accidental early access fails loudly.
  • Declare functions before you call them unless you deliberately rely on declaration hoisting.
  • Treat function expressions and arrow functions like any other value: define first, use second.
  • Remember that hoisting is scope-bound. Inner declarations do not leak outward.

Hoisting is not a quirk to memorise for trivia night. It is the reason JavaScript registers names before it runs your logic. Once you read code with those two phases in mind, the odd output stops looking random and starts looking inevitable.