JavaScript and TypeScript course Β· Module 2: Functions, Arrays and Objects

Hoisting in JavaScript

20 min read
In this lesson8

Welcome back to Jurassic Park! In the lesson on variable scope we examined where a variable lives, that is, what scope it has. Today we will answer an equally important question: from which moment a variable or a function exists at all. The mechanism that decides this is called hoisting, and knowing it protects you from the sneakiest category of bugs in the park systems - the kind that do not blow anything up, but quietly hand over the wrong value. A variable that exists but holds nothing can be more dangerous than a variable that does not exist at all.

What Is Hoisting?

Hoisting (literally "lifting") is JavaScript's behavior of moving variable and function declarations to the top of their scope - function or global - already during the compilation phase, before the first line of code runs. The engine first scans the whole code and makes a list of the names that appear in it, and only then starts executing it. Thanks to this, we can refer to some variables and functions earlier than the place where they were written in the file.

It is a bit like an inventory list drawn up before the dinosaurs arrive in their enclosures: the JavaScript compiler first notes down "what to expect", and only then performs the specific operations, line by line. Only declarations travel to the top - never the values we assign to them.

Every variable goes through the same life cycle. The engine first parses the code, then creates an execution context and reserves memory in it for the declared names (this is exactly the moment of hoisting), and only then executes the code: the variable gets a value and is used. When no reference leads to the value anymore, the garbage collector takes care of the cleanup. In the mark phase it marks everything the program can still reach and treats the rest as ready for removal, and then in the sweep phase it frees that memory.

Function Hoisting

Function declarations are hoisted in their entirety: the function body travels together with the name. So we can call a function before it appears in the file. In park management this is very convenient, because it lets us keep the main logic at the top and push the technical details down to the bottom.

1// We can call the function before it is defined
2checkSecurity("T-Rex Enclosure");  // "Checking security: T-Rex Enclosure"
3
4// The function declaration is hoisted
5function checkSecurity(location) {
6  console.log(`Checking security: ${location}`);
7  return true;
8}

This works because during the compilation phase JavaScript lifts the entire declaration to the top of the scope - the name together with the body. Before the first statement runs, checkSecurity is already a complete function, ready to be called, and not an empty slot waiting to be filled in. This is the only case in the whole language where a value travels to the top of the scope together with the name, because a function is a value too. All the other constructs we are about to see hoist the name alone and nothing else.

Watch Out for Function Expressions!

Only function declarations are hoisted in their entirety. Function expressions, that is, functions assigned to a variable, follow the rules of variable hoisting: the variable itself goes to the top, but the function assigned to it does not.

1// This will NOT work correctly
2// feedDinosaur("Rex");  // TypeError: feedDinosaur is not a function
3
4// Only the variable declaration is hoisted, not the function assignment
5var feedDinosaur = function(dinoName) {
6  console.log(`Feeding dinosaur: ${dinoName}`);
7};
8
9// From this line on we can call the function
10feedDinosaur("Rex");  // "Feeding dinosaur: Rex"

Read the error message carefully, because it is a valuable clue. We do not get a ReferenceError, but TypeError: feedDinosaur is not a function - the name feedDinosaur already exists, it simply holds undefined at that moment, and undefined cannot be called like a function. This small difference can cut debugging from an hour to a minute: a ReferenceError points us to a typo or a forgotten import, while a TypeError points us to the order of the lines in the file. A function declaration is available throughout its whole scope, a function expression only from the line with the assignment.

Arrow Functions Work the Same Way

Arrow functions are expressions too, so their body is never hoisted. Because we usually assign them to const or let, the error for calling them too early turns out to be even firmer than with var.

1// checkFence();  // ReferenceError: Cannot access checkFence before initialization
2
3const checkFence = () => "Fence OK";
4
5console.log(checkFence());  // "Fence OK"

The difference matters: with var we got a TypeError, here a ReferenceError. Both messages mean the same thing - "too early" - but the const version stops the program exactly at the spot of the mistake, instead of passing along an undefined value that will blow up a few dozen lines later, in a completely innocent piece of code. Hence the practical rule: before the line with the definition, you can only use functions declared with function name() {}. Everything we assign to a variable - a classic function expression or an arrow - works only below that line.

Variable Hoisting

With variables, hoisting behaves differently depending on the keyword (var, let, const). This is where most of the surprises come from, so let's break both cases down into their basic parts.

Hoisting of Variables Declared with var

Variables declared with var are hoisted and immediately initialized with the value undefined, so reading them before the declaration is perfectly legal:

1console.log(dinosaurCount);  // undefined (not an error!)
2var dinosaurCount = 15;
3console.log(dinosaurCount);  // 15

Nothing broke - and that is exactly the problem. The program carries on with a value nobody planned, and undefined in the dinosaur counter turns into NaN in every calculation it ends up in. The report will show NaN instead of the number of animals in an enclosure that holds three, and nobody will get any warning. It is easier to understand if we imagine that before running the code, JavaScript rewrites the code above into this form:

1var dinosaurCount;           // Hoisting - the declaration travels to the top
2console.log(dinosaurCount);  // undefined
3dinosaurCount = 15;          // The assignment stays where we wrote it
4console.log(dinosaurCount);  // 15

The declaration jumped to the top, while the assignment stayed exactly where we wrote it. The whole rule of hoisting fits into one sentence: the declaration travels up, the value does not move from its place. The stretch between these two points is a window in which the variable is reachable but empty. A park sensor reporting undefined looks worryingly similar in the logs to a sensor that reports nothing at all - and those are two completely different situations, one meaning all is calm, the other a broken fence.

Hoisting of Variables Declared with let and const

Variables declared with let and const (introduced in ES6) are hoisted too, but they do not get the value undefined. Instead, they land in the temporal dead zone (TDZ) - the period from the moment the block starts executing up to the line in which the variable is actually declared. Reaching for the variable during that time ends with an error.

1// This will cause an error:
2// console.log(securityLevel);  // ReferenceError: Cannot access securityLevel before initialization
3let securityLevel = "High";
4console.log(securityLevel);  // "High"
5
6// The same applies to const:
7// console.log(parkName);  // ReferenceError: Cannot access parkName before initialization
8const parkName = "Jurassic Park";
9console.log(parkName);  // "Jurassic Park"

You need to be able to tell two error messages apart:

  • a variable that was never declared: ReferenceError: xyz is not defined
  • a let or const variable in the temporal dead zone: ReferenceError: Cannot access 'xyz' before initialization

This distinction is a gift for whoever is debugging. The first case means the variable does not exist anywhere - most likely a typo or a forgotten import. The second says the variable exists, we just reached for it too early, so it is enough to move the read below the declaration. Imagine a two-hundred-line function in which a value is read at the top and assigned at the bottom: with var we get a silent undefined and an hour of groping around in the dark, with let the program stops immediately and points a finger at the exact line. The temporal dead zone is not a limitation, but an early warning system.

Classes Also Sit in the Temporal Dead Zone

Classes behave like let and const, not like function declarations. Their name is hoisted, but until the engine executes the class body, every attempt to use the class ends with an error.

1// const dino = new Dinosaur("Rex");  // ReferenceError: Cannot access Dinosaur before initialization
2
3class Dinosaur {
4  constructor(name) {
5    this.name = name;
6  }
7}
8
9const dino = new Dinosaur("Rex");  // OK
10console.log(dino.name);  // "Rex"

So the rule "functions can be defined below the place where they are used" does not extend to classes - a class must be created before the first new that creates an instance of it. If out of habit you push the class definition to the end of the file, this is exactly the message you will see in the console, and the cure is trivial: move the class above the code that instantiates it. In the park's files this gives a simple order - first the models of animals and equipment, then the logic that uses them, and never the other way round.

Hoisting and Variable Scope

Hoisting is tightly intertwined with variable scope. Declarations do not travel to the top of the file, but to the top of their own scope, and which scope counts as "their own" depends on the keyword: var sticks to function scope, let and const to block scope. In the global scope the rule stays the same, except that the top is simply the beginning of the program.

Example with var (Function Scope)

var completely ignores the braces of an if or a loop: the declaration lands at the top of the function that contains it, no matter where we wrote it.

1function monitorSystem() {
2  console.log(systemStatus);  // undefined (hoisting within the function)
3
4  if (true) {
5    var systemStatus = "Online";
6  }
7
8  console.log(systemStatus);  // "Online"
9}
10
11monitorSystem();
12// console.log(systemStatus);  // ReferenceError: systemStatus is not defined (outside the function scope)

The if block did not create a new scope for systemStatus here at all. The declaration jumped to the top of monitorSystem, so the variable can be read even before the block (it is empty at that point), and after the block it already holds the assigned value. Outside the function it disappears completely, because the only boundary that var respects is a function. So two console.log calls with the same name give two different results - not because there are two variables, but because we look at one variable once before the assignment and once after it.

Loops Show the Difference Most Clearly

Loops are the best testing ground for comparing both scopes, because the counter is declared and used in one place. Check what remains after a loop whose counter is created with var, and what remains after a loop with let.

1for (var index = 0; index < 3; index++) {
2  // patrolling the enclosures
3}
4console.log(index);  // 3 - var survives the loop (function scope)
5
6for (let step = 0; step < 3; step++) {
7  // patrolling the enclosures
8}
9// console.log(step);  // ReferenceError - let stays in block scope

After the first loop finishes, index still has the value 3, because it belongs to the function scope and calmly survives the closing brace. The step variable from the second loop has block scope and disappears together with the loop, so reading it below ends with a ReferenceError. This difference is behind the famous counter troubles in loops with a delay: with var all the calls see the same final value, with let each iteration gets its own copy of the counter and keeps its own value.

Example with let and const (Block Scope)

With let and const, every pair of braces creates a new scope, and the temporal dead zone applies to that very block, not to the whole function.

1function checkEnclosures() {
2  // console.log(enclosureStatus);  // ReferenceError (temporal dead zone)
3
4  if (true) {
5    let enclosureStatus = "Secure";
6    console.log(enclosureStatus);  // "Secure"
7  }
8
9  // console.log(enclosureStatus);  // ReferenceError (outside the block scope)
10}
11
12checkEnclosures();

Both commented-out lines would end with an error, but for completely different reasons: the first because the variable is still sitting in the temporal dead zone, the second because the if block has ended and the variable has ceased to exist. The braces are a real wall here, and that is exactly what we want - a block variable stays in its own enclosure. In the exercise waiting for you right after this lesson, you will practice variable scope in different contexts: you will create variables with different scopes - global, function and block - and observe how they behave.

Practical Examples of Hoisting in Jurassic Park

Example 1: Dinosaur Monitoring System

Function hoisting lets you organize a file by importance rather than by execution order. The main procedure goes at the top, the helper functions it uses at the very bottom - and everything still works.

1// Dinosaur monitoring system
2
3// The main functions are defined at the top for clarity
4function initMonitoringSystem() {
5  console.log("Initializing the dinosaur monitoring system...");
6
7  // We can call these functions even though they are defined later
8  const healthStatus = checkDinosaurHealth("Rex");
9  const locationData = trackDinosaur("Blue");
10
11  return {
12    health: healthStatus,
13    location: locationData,
14    systemStatus: "Online"
15  };
16}
17
18// Calling the main function
19const systemStatus = initMonitoringSystem();
20console.log(systemStatus);
21
22// Helper functions defined at the bottom of the file
23function checkDinosaurHealth(dinoName) {
24  console.log(`Checking the health of dinosaur: ${dinoName}`);
25  return "Healthy";
26}
27
28function trackDinosaur(dinoName) {
29  console.log(`Tracking dinosaur: ${dinoName}`);
30  return { x: 135, y: 270, area: "Sector B" };
31}

The call to initMonitoringSystem falls on a line that none of the helper functions has "reached" yet, and still the code runs without a hitch. This is pure function declaration hoisting: both were complete before the first statement ran. Nothing magically moves around while the program is running, and the gain is purely human - whoever opens this file will first read what the system does, and only then how it does it.

Example 2: The Hoisting Trap

The same mechanism can work against us. Hoisting becomes treacherous when an inner function declares a variable with a name that already exists in the outer scope. Read the code carefully and try to predict the result before you look at the explanation.

1// A trap related to hoisting
2
3// Function that checks the security measures
4function checkSecurity() {
5  var fenceStatus = "Offline"; // Hoisted to the top of the function
6
7  function enableFences() {
8    // The local variable is hoisted to the top of enableFences,
9    // but it has no value at all until the initialization line
10    console.log("Current fence status before the change:", fenceStatus); // undefined!
11
12    // The local fenceStatus variable shadows the one from the outer scope
13    var fenceStatus = "Online";
14    console.log("New fence status:", fenceStatus); // "Online"
15  }
16
17  enableFences();
18  console.log("Fence status in the main function:", fenceStatus); // "Offline" (not changed)
19}
20
21checkSecurity();

A subtle trap is hidden here: the console.log inside enableFences prints undefined, because the local fenceStatus variable was hoisted to the top of the inner function and shadows the one from the outer scope from the very first line, even though it only gets its value further down. The outer variable never changes either - it keeps the value "Offline" to the end. In a real park this would look like the operator panel announcing that the fences are switched on, while the main security function still considers them switched off.

Example 3: A Better Approach with let and const

Replacing var with let and const eliminates this whole class of problems, because reading too early stops the program instead of quietly producing undefined.

1// A better approach using let and const
2
3function parkOperations() {
4  // We use const for values that should not change
5  const maxVisitors = 2000;
6
7  // We use let for variables that will change
8  let currentVisitors = 0;
9
10  function admitVisitors(count) {
11    // With let and const, reading a variable before its declaration ends with an error,
12    // which makes it much easier to catch the problem
13
14    // This would cause an error:
15    // console.log(availableSpace);  // ReferenceError: Cannot access before initialization
16
17    // First we declare, then we use
18    const availableSpace = maxVisitors - currentVisitors;
19    if (count <= availableSpace) {
20      currentVisitors += count;
21      console.log(`Admitted ${count} visitors. Currently in the park: ${currentVisitors}`);
22      return true;
23    }
24
25    console.log(`Too many visitors! Available places: ${availableSpace}`);
26    return false;
27  }
28
29  // Testing our function
30  admitVisitors(500);  // Admitted 500 visitors
31  admitVisitors(1000); // Admitted 1000 visitors
32  admitVisitors(700);  // Too many visitors! Available places: 500
33
34  return currentVisitors;
35}
36
37console.log(`Total number of visitors: ${parkOperations()}`);

Let's check what has not changed here: maxVisitors and currentVisitors are still hoisted, because every declaration is subject to hoisting. What has changed is the reaction to a mistake - the temporal dead zone turns a read that comes too early into a loud error instead of a discreet undefined. Every value is created exactly where it is needed, and not a moment earlier, so the gate logic reads from top to bottom without you having to keep the hoisting rules in your head. That is what it is all about in practice: not memorizing the mechanism, but writing code in which the mechanism never surprises you.

Practical Tips About Hoisting

  1. Always declare variables at the top of their scope - this minimizes hoisting-related problems and makes the code much easier to read.

  2. Prefer let and const over var - errors from the temporal dead zone are incomparably easier to diagnose than mysterious undefined values that surface a few hundred lines later.

  3. Declare functions before you use them - function hoisting works, but the natural reading order still remains the clearest for the person who comes back to this code in six months.

  4. Avoid function declarations inside conditional blocks - the behavior can differ between environments, so in that case it is safer to use a function expression assigned to a variable.

  5. Remember that hoisting applies to declarations, not to initialization - a value is assigned exactly where we wrote the assignment, never earlier.

A Bigger Example: The Park Incident Management System

Below you will find a more complete incident management system for Jurassic Park, which brings together everything from this lesson in one place: hoisted function declarations, a var variable at module level, and let and const used for state that changes and for state that must not change.

1// Jurassic Park incident management system
2
3// Using hoisting - we declare the variables at the beginning
4var activeIncidents = [];
5let alertLevel = "Normal";
6const MAX_INCIDENTS = 5;
7
8// The main function for initializing the system
9function initIncidentSystem() {
10  // This variable is hoisted only within this function
11  var systemStatus = "Initializing";
12
13  // We use function hoisting - we can call them before their definition
14  logSystemStatus();
15  resetIncidents();
16
17  systemStatus = "Online";
18  logSystemStatus();
19
20  // A local function - visible only inside initIncidentSystem
21  function logSystemStatus() {
22    console.log(`System status: ${systemStatus}`);
23  }
24
25  return {
26    reportIncident,      // A reference to a function defined below
27    checkAlertLevel,     // A function defined below
28    getActiveIncidents,  // A function defined below
29    resetIncidents       // A function defined inside initIncidentSystem
30  };
31
32  // A local function that we return as part of the API
33  function resetIncidents() {
34    console.log("Resetting the incident list...");
35    activeIncidents = [];
36    updateAlertLevel();
37  }
38}
39
40// Function declarations are hoisted, so they can be called before their definition
41// Function for reporting a new incident
42function reportIncident(location, type, severity) {
43  console.log(`Incident report: ${type} at location ${location} (threat level: ${severity})`);
44
45  const incident = {
46    id: generateIncidentId(),
47    location,
48    type,
49    severity,
50    timestamp: new Date().toISOString(),
51    status: "Active"
52  };
53
54  activeIncidents.push(incident);
55
56  if (activeIncidents.length > MAX_INCIDENTS) {
57    declareEmergency();
58  } else {
59    updateAlertLevel();
60  }
61
62  return incident;
63}
64
65// Generating a unique incident ID
66function generateIncidentId() {
67  return `INC-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
68}
69
70// Updating the alert level based on the active incidents
71function updateAlertLevel() {
72  const highSeverityCount = activeIncidents.filter(inc => inc.severity === "High").length;
73
74  if (highSeverityCount >= 3) {
75    alertLevel = "Critical";
76  } else if (highSeverityCount > 0 || activeIncidents.length >= 3) {
77    alertLevel = "Elevated";
78  } else if (activeIncidents.length > 0) {
79    alertLevel = "Caution";
80  } else {
81    alertLevel = "Normal";
82  }
83
84  console.log(`Alert level updated: ${alertLevel}`);
85}
86
87// Function called when there are too many incidents
88function declareEmergency() {
89  alertLevel = "Evacuation";
90  console.log("WARNING! Too many active incidents. We are declaring a park evacuation!");
91  // Code for initiating the evacuation protocols...
92}
93
94// Function for checking the current alert level
95function checkAlertLevel() {
96  return {
97    level: alertLevel,
98    incidentCount: activeIncidents.length,
99    timestamp: new Date().toISOString()
100  };
101}
102
103// Function that returns the list of active incidents
104function getActiveIncidents() {
105  return [...activeIncidents]; // We return a copy so that nobody modifies the original
106}
107
108// Initializing the system
109const incidentSystem = initIncidentSystem();
110
111// Testing the system
112console.log("===== TESTING THE INCIDENT MANAGEMENT SYSTEM =====");
113
114// Checking the initial state
115console.log("Initial alert level:", incidentSystem.checkAlertLevel().level);
116
117// Reporting incidents
118incidentSystem.reportIncident("T-Rex Enclosure", "Fence damage", "High");
119incidentSystem.reportIncident("Visitor Center", "Power outage", "Medium");
120
121// Checking the state after the reports
122console.log("Active incidents:", incidentSystem.getActiveIncidents().length);
123console.log("Current alert level:", incidentSystem.checkAlertLevel().level);
124
125// Adding more high-priority incidents
126incidentSystem.reportIncident("Laboratory", "Genetic material leak", "High");
127incidentSystem.reportIncident("Velociraptor Enclosure", "Escape attempt", "High");
128
129// This should trigger the evacuation state
130incidentSystem.reportIncident("Sector B", "Dinosaur outside the enclosure", "High");
131incidentSystem.reportIncident("Operations Center", "Security system failure", "High");
132
133// Resetting the system
134incidentSystem.resetIncidents();
135console.log("After the reset - alert level:", incidentSystem.checkAlertLevel().level);

Trace the order of events in this file. initIncidentSystem calls logSystemStatus and resetIncidents before either of them appears in the code, and resetIncidents even sits after the return statement - yet everything works, because function declarations are complete before the first line of the body executes. Variables are a different story: var systemStatus can be read from the very beginning of the function, but it only gets the value "Initializing" on its own line, while activeIncidents, alertLevel and MAX_INCIDENTS were deliberately placed at the very top of the file so that nobody reaches for them too early.

Summary

Hoisting is an important JavaScript concept that shapes the way our code is executed:

  1. Function declarations are hoisted in their entirety - you can use them before the declaration.

  2. var variables are hoisted and initialized with the value undefined - you can refer to them earlier, but until the assignment they carry no useful value.

  3. let and const variables (and classes along with them) are hoisted, but they stay in the temporal dead zone - an attempt to read them before the declaration ends with an error.

  4. Function expressions, including arrow functions, follow the rules for variables - the variable is hoisted, the assigned function is not.

Understanding hoisting is key to writing predictable JavaScript code. It works exactly like in Jurassic Park itself - knowing the rules and protocols is enough for the day to pass without surprises or dangerous situations.

"Hoisting in JavaScript is like the park's morning start-up," says Dr. Rex. "Before the visitors arrive, before the code runs, part of the preparation happens on its own: the protocols are ready, the procedures are in place. But the real data still has to be collected as you go, and reaching for it too early will tell you absolutely nothing!"

In one of the next lessons we will take a close look at the this keyword - another fundamental element of JavaScript, and one that can cause quite a bit of confusion.

Code for this lesson: index.js
1// Hoisting in JavaScript - Jurassic Park
2console.log("Hoisting Laboratory");
3console.log("Understanding hoisting in JavaScript\n");
4
5// ===========================================
6// 1. WHAT IS HOISTING?
7// ===========================================
8console.log("=== 1. Introduction to hoisting ===");
9
10// JavaScript "hoists" declarations to the top of the scope
11// We can use a function before its declaration!
12
13roarDinosaur("T-Rex"); // Works!
14
15function roarDinosaur(name) {
16  console.log(`${name} says: ROAAARRR!`);
17}
18
19roarDinosaur("Velociraptor");
20
21console.log("\nThe function works before its declaration thanks to hoisting!");
22
23// ===========================================
24// 2. FUNCTION HOISTING
25// ===========================================
26console.log("\n=== 2. Function hoisting ===");
27
28// Function declarations are hoisted
29feedDinosaur("Triceratops"); // Works
30
31function feedDinosaur(dino) {
32  console.log(`Feeding: ${dino}`);
33}
34
35// Function expressions are NOT hoisted
36// feedDino2("Stegosaurus"); // ERROR!
37
38const feedDino2 = function(dino) {
39  console.log(`Feeding: ${dino}`);
40};
41
42feedDino2("Stegosaurus"); // Now works
43
44// Arrow functions are also NOT hoisted
45// feedDino3("Brachiosaurus"); // ERROR!
46
47const feedDino3 = (dino) => {
48  console.log(`Feeding: ${dino}`);
49};
50
51feedDino3("Brachiosaurus"); // Now works
52
53// ===========================================
54// 3. VAR HOISTING
55// ===========================================
56console.log("\n=== 3. Hoisting var variables ===");
57
58console.log("\nBefore declaration:");
59console.log("dinoName:", typeof dinoName); // undefined (not an error!)
60
61var dinoName = "T-Rex";
62
63console.log("After declaration:");
64console.log("dinoName:", dinoName); // T-Rex
65
66// JavaScript actually interprets this as:
67// var dinoName; // Declaration hoisted to the top
68// console.log(dinoName); // undefined
69// dinoName = "T-Rex"; // Assignment
70
71// ===========================================
72// 4. HOISTING LET AND CONST
73// ===========================================
74console.log("\n=== 4. Hoisting let and const ===");
75
76// let and const are also hoisted, but...
77// are in the "Temporal Dead Zone" (TDZ)
78
79console.log("\nAttempt to use before declaration:");
80
81// console.log(dinoSpecies); // ReferenceError: Cannot access before initialization
82// console.log(parkName); // ReferenceError: Cannot access before initialization
83
84let dinoSpecies = "Velociraptor";
85const parkName = "Jurassic Park";
86
87console.log("After declaration:");
88console.log("dinoSpecies:", dinoSpecies);
89console.log("parkName:", parkName);
90
91// ===========================================
92// 5. TEMPORAL DEAD ZONE (TDZ)
93// ===========================================
94console.log("\n=== 5. Temporal Dead Zone (TDZ) ===");
95
96function demonstrateTDZ() {
97  console.log("\nInside function - before declaration:");
98
99  // This zone is the TDZ for 'dinosaur'
100  // console.log(dinosaur); // ERROR in the TDZ
101
102  let dinosaur = "Triceratops";
103
104  console.log("After declaration:", dinosaur); // Works
105}
106
107demonstrateTDZ();
108
109// ===========================================
110// 6. DIFFERENCES: var vs let vs const
111// ===========================================
112console.log("\n=== 6. Comparison: var vs let vs const ===");
113
114function compareHoisting() {
115  console.log("\nHoisting test:");
116
117  // var - hoisted with value undefined
118  console.log("var dinosaur1:", typeof dinosaur1); // undefined
119  var dinosaur1 = "T-Rex";
120  console.log("var dinosaur1:", dinosaur1); // T-Rex
121
122  // let - hoisted, but in the TDZ
123  // console.log("let dinosaur2:", dinosaur2); // ERROR: TDZ
124  let dinosaur2 = "Velociraptor";
125  console.log("let dinosaur2:", dinosaur2); // Velociraptor
126
127  // const - hoisted, but in the TDZ
128  // console.log("const dinosaur3:", dinosaur3); // ERROR: TDZ
129  const dinosaur3 = "Triceratops";
130  console.log("const dinosaur3:", dinosaur3); // Triceratops
131}
132
133compareHoisting();
134
135// ===========================================
136// 7. HOISTING IN BLOCKS
137// ===========================================
138console.log("\n=== 7. Hoisting in blocks ===");
139
140function blockHoisting() {
141  console.log("\nBefore the block:");
142  console.log("globalVar:", typeof globalVar); // undefined
143  // console.log("blockLet:", typeof blockLet); // ERROR
144
145  var globalVar = "Available everywhere";
146
147  if (true) {
148    console.log("\nIn block:");
149    let blockLet = "Only in the block";
150    const blockConst = "Only in the block (const)";
151
152    console.log("blockLet:", blockLet);
153    console.log("blockConst:", blockConst);
154    console.log("globalVar:", globalVar);
155  }
156
157  console.log("\nAfter block:");
158  console.log("globalVar:", globalVar); // Works
159  // console.log("blockLet:", blockLet); // ERROR
160  // console.log("blockConst:", blockConst); // ERROR
161}
162
163blockHoisting();
164
165// ===========================================
166// 8. HOISTING IN LOOPS
167// ===========================================
168console.log("\n=== 8. Hoisting in loops ===");
169
170console.log("\nLoop with var:");
171for (var i = 0; i < 3; i++) {
172  console.log(`  Iteration ${i + 1}`);
173}
174console.log("After loop - i:", i); // Works (i = 3)
175
176console.log("\nLoop with let:");
177for (let j = 0; j < 3; j++) {
178  console.log(`  Iteration ${j + 1}`);
179}
180// console.log("After loop - j:", j); // ERROR: j is not defined
181
182// ===========================================
183// 9. PRACTICAL HOISTING PROBLEMS
184// ===========================================
185console.log("\n=== 9. Common pitfalls ===");
186
187// Problem 1: Overriding function
188function problemExample1() {
189  console.log("\nProblem 1: Overriding");
190
191  function greet() {
192    console.log("  First greeting");
193  }
194
195  greet(); // Which function will be called?
196
197  function greet() {
198    console.log("  Second greeting (this will be used!)");
199  }
200
201  // JavaScript "hoists" both declarations,
202  // but the second overrides the first
203}
204
205problemExample1();
206
207// Problem 2: var in a loop with setTimeout
208console.log("\nProblem 2: var in a loop with setTimeout");
209
210console.log("With var (all will show 3):");
211for (var k = 0; k < 3; k++) {
212  setTimeout(function() {
213    console.log(`  var k = ${k}`);
214  }, 100);
215}
216
217setTimeout(() => {
218  console.log("\nWith let (will show 0, 1, 2):");
219  for (let m = 0; m < 3; m++) {
220    setTimeout(function() {
221      console.log(`  let m = ${m}`);
222    }, 100);
223  }
224}, 200);
225
226// ===========================================
227// 10. BEST PRACTICES
228// ===========================================
229console.log("\n=== 10. Best practices ===");
230
231// BEST PRACTICES:
232
233// 1. Declare variables at the beginning of scope
234function goodPractice1() {
235  const parkName = "Jurassic Park";
236  let dinosaurCount = 0;
237  let totalVisitors = 0;
238
239  dinosaurCount = 15;
240  totalVisitors = 1000;
241
242  console.log(`\n${parkName}: ${dinosaurCount} dinosaurs, ${totalVisitors} visitors`);
243}
244
245goodPractice1();
246
247// 2. Use const by default, let when needed
248const SECURITY_LEVEL = "HIGH";
249let currentStatus = "OPERATIONAL";
250
251console.log(`Security: ${SECURITY_LEVEL}`);
252console.log(`Status: ${currentStatus}`);
253
254// 3. Avoid var
255// var oldWay = "Don't use this!"; // BAD
256
257// 4. Declare functions before use (readability)
258function wellOrganized() {
259  // Declarations at the beginning
260  const MAX_CAPACITY = 50;
261  let currentCapacity = 0;
262
263  // Helper functions
264  function addDinosaur(name) {
265    if (currentCapacity < MAX_CAPACITY) {
266      currentCapacity++;
267      console.log(`  Added ${name}. Capacity: ${currentCapacity}/${MAX_CAPACITY}`);
268    } else {
269      console.log(`  No space for ${name}`);
270    }
271  }
272
273  // Main logic
274  console.log("\nSystem capacity management:");
275  addDinosaur("T-Rex");
276  addDinosaur("Velociraptor");
277  addDinosaur("Triceratops");
278}
279
280wellOrganized();
281
282// ===========================================
283// 11. COMPLEX EXAMPLE
284// ===========================================
285console.log("\n=== 11. Monitoring system - complex example ===");
286
287function createMonitoringSystem() {
288  // Declarations at the beginning (best practice)
289  const systemName = "Dino Monitor v1.0";
290  let isActive = false;
291  let alertCount = 0;
292
293  // Helper functions
294  function activate() {
295    isActive = true;
296    console.log(`\n${systemName} activated`);
297  }
298
299  function deactivate() {
300    isActive = false;
301    console.log(`${systemName} deactivated`);
302  }
303
304  function sendAlert(message) {
305    if (isActive) {
306      alertCount++;
307      console.log(`Alert #${alertCount}: ${message}`);
308    } else {
309      console.log("System inactive - alert skipped");
310    }
311  }
312
313  function getStats() {
314    console.log(`\nStatistics for ${systemName}:`);
315    console.log(`   Status: ${isActive ? "Active" : "Inactive"}`);
316    console.log(`   Alerts sent: ${alertCount}`);
317  }
318
319  // Main interface
320  return {
321    start: activate,
322    stop: deactivate,
323    alert: sendAlert,
324    stats: getStats
325  };
326}
327
328const monitor = createMonitoringSystem();
329
330monitor.alert("Test before activation"); // Skipped
331monitor.start();
332monitor.alert("T-Rex is approaching the fence");
333monitor.alert("Security level critical");
334monitor.stats();
335monitor.stop();
336monitor.alert("Test after deactivation"); // Skipped
337monitor.stats();
338
339console.log("\nCongratulations! You've mastered hoisting in JavaScript!");

Spotted a mistake in this lesson?

Hands-on tasks in the game

  • Code editor

    Declare a global constant parkName = 'Jurassic Park'. Write a function checkEnclosure() that creates a local constant enclosureName = 'Zone A' and, inside an if (true) block, creates a variable alarmLevel = 5 with let and displays parkName, enclosureName and alarmLevel in the console. The function returns enclosureName. Call checkEnclosure(). Outside the function, enclosureName and alarmLevel must not exist.

Useful articles