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

Variable Scope (Global, Function, Block)

11 min read
In this lesson8

Welcome back to Jurassic Park! Today we will tackle one of the fundamental concepts of JavaScript - variable scope. Understanding variable scope is key to writing efficient, safe and predictable code, especially in complex systems such as our park full of dinosaurs.

What Is Variable Scope?

Variable scope determines where in the code a given variable is available - in other words, from which places in the code we can refer to it. Just as Jurassic Park has zones with different access levels (the visitor zone, the laboratory for scientists only, the enclosures that require special permissions), JavaScript has different scopes of access to variables.

In JavaScript we distinguish three main types of scope:

  1. Global scope
  2. Function scope
  3. Block scope

Global Scope

Variables defined outside of any function or block of code have global scope. This means they are available throughout the whole program, in every function and every block of code.

1// Global variables - available everywhere, like public information about the park
2const parkName = "Jurassic Park";
3let visitorCount = 0;
4var isOpenToPublic = true;
5
6function checkParkStatus() {
7  console.log(`Park name: ${parkName}`);
8  console.log(`Number of visitors: ${visitorCount}`);
9  console.log(`Open to visitors: ${isOpenToPublic}`);
10}
11
12function updateVisitorCount(newCount) {
13  // We can modify a global variable from any function
14  visitorCount = newCount;
15  console.log(`Number of visitors updated: ${visitorCount}`);
16}
17
18// Calling the functions that use the global variables
19checkParkStatus();
20updateVisitorCount(250);
21checkParkStatus();

You should be careful with global variables, because:

  • They can be accidentally modified from anywhere in the code
  • They can lead to name conflicts
  • They make it harder to track the state of the application
  • They increase the coupling between different parts of the code

Global variables in our park are like documents available to all employees - sometimes they are essential, but too many such documents can lead to mess and misunderstandings.

Function Scope

Variables defined inside a function are available only inside that function. They are local variables of that function.

1function monitorDinosaur(dinoId) {
2  // These variables have function scope - they are available only inside this function
3  const startTime = Date.now();
4  let status = "Monitoring...";
5  var sensorData = [];
6
7  // We can use the variables inside the function
8  status = "Collecting data...";
9  sensorData.push({ time: startTime, temperature: 37.5 });
10
11  console.log(`Status: ${status}`);
12  console.log(`Sensor data: ${JSON.stringify(sensorData)}`);
13
14  // We return the result
15  return {
16    id: dinoId,
17    monitoringStarted: startTime,
18    lastStatus: status
19  };
20}
21
22// Calling the function
23const monitoringResults = monitorDinosaur("T-Rex-01");
24
25// The code below would cause an error, because we are trying to access
26// variables with function scope from outside the function
27// console.log(startTime);    // ReferenceError
28// console.log(status);       // ReferenceError
29// console.log(sensorData);   // ReferenceError
30
31// But we do have access to the value returned by the function
32console.log(monitoringResults);

Function scope works like the restricted zones in the park - only authorized personnel (the code inside the function) have access to the information and resources in that zone.

The Difference Between var, let and const in the Context of Function Scope

All three keywords (var, let, const) create variables with function scope, but there is an important difference between them:

  • var has function scope, but not block scope
  • let and const have both function scope and block scope

Let's see this in an example:

1function securityCheck() {
2  // All these variables are available throughout the whole function
3  var securityCode = "A1B2C3";
4  let accessLevel = 5;
5  const securityChief = "Robert Muldoon";
6
7  if (accessLevel >= 5) {
8    var emergencyProtocol = "Code Red"; // function scope!
9    let backupSystem = "Operational";   // block scope
10    const safeRoom = "Bunker B";        // block scope
11
12    console.log(securityCode);    // A1B2C3 - available
13    console.log(accessLevel);     // 5 - available
14    console.log(securityChief);   // Robert Muldoon - available
15  }
16
17  console.log(emergencyProtocol); // Code Red - available even though it was defined in the if block
18  // console.log(backupSystem);   // ReferenceError - the variable has block scope
19  // console.log(safeRoom);       // ReferenceError - the variable has block scope
20}
21
22securityCheck();

Block Scope

Block scope was introduced in ES6 (ES2015) together with the new keywords let and const. Variables defined with let and const inside a block of code (delimited by curly braces {}) are available only inside that block.

1function monitorDinosaurBehavior() {
2  let dangerLevel = 0;
3
4  // The if block - creates a new scope
5  if (true) {
6    // Block scope - variables available only inside this block
7    let behavior = "Calm";
8    const vitals = { heartRate: 80, temperature: 38 };
9
10    // Modifying a variable from the outer scope
11    dangerLevel = behavior === "Aggressive" ? 5 : 1;
12
13    console.log(behavior);     // Calm - available
14    console.log(vitals);       // { heartRate: 80, temperature: 38 } - available
15    console.log(dangerLevel);  // 1 - available
16  }
17
18  console.log(dangerLevel);    // 1 - available because it was defined in a wider scope
19  // console.log(behavior);    // ReferenceError - the variable has block scope
20  // console.log(vitals);      // ReferenceError - the variable has block scope
21
22  // The for block - also creates a new scope
23  for (let i = 0; i < 5; i++) {
24    // 'i' has block scope, available only inside the loop
25    const sensorId = `Sensor-${i}`;
26    console.log(sensorId);
27  }
28
29  // console.log(i);        // ReferenceError - 'i' has block scope
30  // console.log(sensorId); // ReferenceError - the variable has block scope
31}
32
33monitorDinosaurBehavior();

Block scope is like the individual enclosures in our park - each enclosure has its own rules and access, and the equipment assigned to one enclosure is not available in the other enclosures.

The Difference Between var, let and const

Now we can better understand the differences between these three keywords:

  1. var:

    • Has function or global scope, but not block scope
    • Can be redeclared multiple times
    • Can be modified
    • Is hoisted with the value undefined assigned to it
  2. let:

    • Has block scope
    • Cannot be redeclared in the same scope
    • Can be modified
    • Is hoisted without a value being assigned (temporal dead zone)
  3. const:

    • Has block scope
    • Cannot be redeclared in the same scope
    • Cannot be modified (but the properties of objects can be modified)
    • Is hoisted without a value being assigned (temporal dead zone)
1function demonstrateVariables() {
2  // var
3  var dinosaur = "T-Rex";
4  var dinosaur = "Velociraptor"; // OK - can be redeclared
5  dinosaur = "Triceratops";      // OK - can be modified
6
7  // let
8  let park = "Jurassic Park";
9  // let park = "Dino World";    // Error - cannot be redeclared
10  park = "Jurassic World";       // OK - can be modified
11
12  // const
13  const island = "Isla Nublar";
14  // const island = "Isla Sorna"; // Error - cannot be redeclared
15  // island = "Another Island";   // Error - cannot be modified
16
17  // But the properties of const objects can be modified
18  const parkSystem = {
19    mainPower: true,
20    backupGenerator: false
21  };
22
23  // This is allowed
24  parkSystem.mainPower = false;
25  parkSystem.backupGenerator = true;
26
27  console.log(parkSystem); // { mainPower: false, backupGenerator: true }
28}
29
30demonstrateVariables();

Lexical Scope and Closures

JavaScript uses lexical scope, which means that functions can access variables defined in their outer scope.

1function initSecuritySystem() {
2  // Variables in the scope of the outer function
3  const systemName = "JurassicSafe";
4  let isActive = false;
5  let alertLevel = 0;
6
7  // The inner function has access to the variables of the outer function
8  function activateSystem() {
9    isActive = true;
10    console.log(`System ${systemName} has been activated.`);
11    return true;
12  }
13
14  // One more inner function
15  function setAlertLevel(level) {
16    alertLevel = level;
17    console.log(`System ${systemName}: alert level set to ${alertLevel}`);
18  }
19
20  // We return the inner functions
21  return {
22    activate: activateSystem,
23    setAlert: setAlertLevel,
24    getStatus: function() {
25      return {
26        name: systemName,
27        active: isActive,
28        alertLevel: alertLevel
29      };
30    }
31  };
32}
33
34// We create an instance of the security system
35const securitySystem = initSecuritySystem();
36
37// We use the returned functions
38securitySystem.activate();           // System JurassicSafe has been activated.
39securitySystem.setAlert(3);          // System JurassicSafe: alert level set to 3
40console.log(securitySystem.getStatus()); // { name: "JurassicSafe", active: true, alertLevel: 3 }

In the example above, the inner functions form a closure over the variables of the initSecuritySystem function. Even after initSecuritySystem has finished executing, the returned functions still have access to those variables.

Closures are like the security teams in the park - even when they leave the command center, they still have access to the codes and protocols they took with them.

Practical Example: A Park Management System

Let's build a more comprehensive example that demonstrates the different variable scopes in the context of a park management system:

1// Global variables - available throughout the whole program
2const PARK_NAME = "Jurassic Park";
3let parkStatus = "closed";
4var totalVisitors = 0;
5
6// An IIFE (Immediately Invoked Function Expression) that creates a module
7const parkSystem = (function() {
8  // Private variables - function scope, inaccessible from outside the module
9  let securityStatus = "active";
10  let powerStatus = "online";
11  const MAX_CAPACITY = 2000;
12
13  // Private helper function
14  function checkSystemStatus() {
15    return powerStatus === "online" && securityStatus === "active";
16  }
17
18  // The module's public API
19  return {
20    // Method that opens the park to visitors
21    openPark: function() {
22      if (checkSystemStatus()) {
23        parkStatus = "open"; // Modifying a global variable
24        console.log(`${PARK_NAME} is now open to visitors!`);
25        return true;
26      } else {
27        console.log("Cannot open the park - the security systems are not fully operational.");
28        return false;
29      }
30    },
31
32    // Method that registers new visitors
33    registerVisitors: function(count) {
34      // Checking the park's capacity
35      if (parkStatus !== "open") {
36        console.log("The park is closed. Cannot register visitors.");
37        return false;
38      }
39
40      // A variable with block scope
41      const availableCapacity = MAX_CAPACITY - totalVisitors;
42
43      if (count <= availableCapacity) {
44        totalVisitors += count; // Modifying a global variable
45        console.log(`Registered ${count} new visitors. Total: ${totalVisitors}`);
46
47        // Block scope inside the condition
48        if (totalVisitors > MAX_CAPACITY * 0.8) {
49          const warningMessage = "Warning: The park is approaching maximum capacity!";
50          console.log(warningMessage);
51        }
52
53        return true;
54      } else {
55        console.log(`Error: Too many visitors. Available capacity: ${availableCapacity}`);
56        return false;
57      }
58    },
59
60    // Method that closes the park
61    closePark: function() {
62      // A local function with access to variables from a wider scope
63      function evacuateVisitors() {
64        console.log(`Evacuating ${totalVisitors} visitors from the park...`);
65        totalVisitors = 0;
66      }
67
68      parkStatus = "closed";
69      evacuateVisitors();
70      console.log(`${PARK_NAME} has been closed.`);
71
72      return true;
73    },
74
75    // Method that changes the status of the systems
76    updateSystems: function(power, security) {
77      // To demonstrate let vs var
78      if (power) {
79        let localMessage = "Updating the power system...";
80        console.log(localMessage);
81        powerStatus = power;
82      }
83
84      if (security) {
85        var statusMessage = "Updating the security system...";
86        console.log(statusMessage);
87        securityStatus = security;
88      }
89
90      // console.log(localMessage); // Error: localMessage has block scope
91      console.log(statusMessage);  // Works: statusMessage has function scope
92
93      return {
94        power: powerStatus,
95        security: securityStatus
96      };
97    }
98  };
99})();
100
101// Using our system
102console.log(`Park status before opening: ${parkStatus}`);
103parkSystem.openPark();
104console.log(`Park status after opening: ${parkStatus}`);
105
106parkSystem.registerVisitors(500);
107parkSystem.registerVisitors(1200);  // Should show a capacity warning
108parkSystem.registerVisitors(400);   // Should show an error about exceeding capacity
109
110// Updating the systems
111parkSystem.updateSystems("offline", null);
112parkSystem.openPark(); // Should show an error - the park cannot be opened
113
114// Restoring the systems and closing the park
115parkSystem.updateSystems("online", "active");
116parkSystem.closePark();
117console.log(`Park status after closing: ${parkStatus}`);
118console.log(`Number of visitors after the evacuation: ${totalVisitors}`);
119
120// Trying to access the private variables
121// console.log(securityStatus); // Error: variable not accessible
122// console.log(MAX_CAPACITY);   // Error: variable not accessible

Good Practice Rules

  1. Avoid global variables - just like in a real park, the more public areas there are, the harder they are to monitor and secure.

  2. Prefer const over let, and let over var - most variables should not change their values, and if they do, they should have the smallest possible scope.

  3. Use block scope - limit the visibility of variables to the smallest possible scope.

  4. Apply the module pattern - encapsulate variables and functions in modules, exposing only the necessary API.

  5. Be aware of closures - closures are a powerful tool, but they can lead to memory leaks if they are used incorrectly.

Summary

Variable scope in JavaScript is like the system of access zones in Jurassic Park:

  • Global scope - like the public areas of the park, available to everyone
  • Function scope - like the zones restricted to specific employees
  • Block scope - like the individual enclosures and laboratories with their own access system

Understanding variable scope lets us write better organized, safer and more predictable code - just as good zone management in a park full of dangerous dinosaurs makes everyone safer.

In one of the upcoming lessons we will dive into hoisting, which is closely related to variable scope and has a significant impact on how JavaScript code works.

Code for this lesson: index.js
1// Variable Scope - Jurassic Park
2console.log("Variable Scope Laboratory");
3console.log("Understanding scope in JavaScript\n");
4
5// ===========================================
6// 1. GLOBAL SCOPE
7// ===========================================
8console.log("=== 1. Global scope ===");
9
10const parkName = "Jurassic Park"; // Global variable
11let totalDinosaurs = 0; // Global variable
12
13function addDinosaur(name) {
14  totalDinosaurs++; // Access to global variable
15  console.log(`Added ${name}. Total dinosaurs: ${totalDinosaurs}`);
16  console.log(`   In the park: ${parkName}`);
17}
18
19addDinosaur("T-Rex");
20addDinosaur("Velociraptor");
21addDinosaur("Triceratops");
22
23console.log(`\nPark ${parkName} has ${totalDinosaurs} dinosaurs`);
24
25// ===========================================
26// 2. FUNCTION SCOPE
27// ===========================================
28console.log("\n=== 2. Function scope ===");
29
30function feedDinosaur(dinoName) {
31  const foodAmount = 50; // Local variable of the function
32  const message = `Feeding ${dinoName} (portion: ${foodAmount}kg)`;
33
34  console.log(`${message}`);
35  return foodAmount;
36}
37
38feedDinosaur("T-Rex");
39feedDinosaur("Velociraptor");
40
41// console.log(foodAmount); // ERROR: foodAmount is not defined
42// console.log(message); // ERROR: message is not defined
43
44// ===========================================
45// 3. BLOCK SCOPE (let & const)
46// ===========================================
47console.log("\n=== 3. Block scope (let & const) ===");
48
49function checkSecurity(sector) {
50  console.log(`\nChecking security of sector ${sector}`);
51
52  if (sector === "A") {
53    let dangerLevel = "HIGH"; // Block-scoped variable
54    const dinosaurType = "T-Rex"; // Block-scoped variable
55
56    console.log(`   Danger level: ${dangerLevel}`);
57    console.log(`   Dinosaur type: ${dinosaurType}`);
58  }
59
60  // console.log(dangerLevel); // ERROR: dangerLevel is not defined
61  // console.log(dinosaurType); // ERROR: dinosaurType is not defined
62
63  console.log(`   Sector ${sector} check completed`);
64}
65
66checkSecurity("A");
67checkSecurity("B");
68
69// Example with for loop
70console.log("\nLoop with let:");
71for (let i = 0; i < 3; i++) {
72  console.log(`  Iteration ${i + 1}`);
73}
74// console.log(i); // ERROR: i is not defined
75
76// ===========================================
77// 4. var vs let vs const
78// ===========================================
79console.log("\n=== 4. Differences: var vs let vs const ===");
80
81function demonstrateScope() {
82  console.log("\nScope demonstration:");
83
84  // var - function scope (old way)
85  if (true) {
86    var dinoVar = "T-Rex (var)";
87    let dinoLet = "Velociraptor (let)";
88    const dinoConst = "Triceratops (const)";
89
90    console.log(`  In block - var: ${dinoVar}`);
91    console.log(`  In block - let: ${dinoLet}`);
92    console.log(`  In block - const: ${dinoConst}`);
93  }
94
95  console.log(`\n  Outside block - var: ${dinoVar}`); // Works!
96  // console.log(`  Outside block - let: ${dinoLet}`); // ERROR
97  // console.log(`  Outside block - const: ${dinoConst}`); // ERROR
98}
99
100demonstrateScope();
101
102// ===========================================
103// 5. CLOSURE
104// ===========================================
105console.log("\n=== 5. Closures ===");
106
107function createDinosaurCounter(startCount = 0) {
108  let count = startCount; // Private variable
109
110  return {
111    add: function(name) {
112      count++;
113      console.log(`Added ${name}. Count: ${count}`);
114    },
115    remove: function(name) {
116      if (count > 0) {
117        count--;
118        console.log(`Removed ${name}. Count: ${count}`);
119      }
120    },
121    getCount: function() {
122      return count;
123    }
124  };
125}
126
127const sectorA = createDinosaurCounter(5);
128const sectorB = createDinosaurCounter(0);
129
130console.log("\nSector A:");
131sectorA.add("T-Rex");
132sectorA.add("Velociraptor");
133console.log(`Total in sector A: ${sectorA.getCount()}`);
134
135console.log("\nSector B:");
136sectorB.add("Triceratops");
137console.log(`Total in sector B: ${sectorB.getCount()}`);
138
139// ===========================================
140// 6. NESTED FUNCTIONS
141// ===========================================
142console.log("\n=== 6. Nested functions and scope ===");
143
144function managePark() {
145  const parkName = "Isla Nublar";
146  let visitors = 1000;
147
148  function openPark() {
149    console.log(`\n${parkName} open for ${visitors} visitors`);
150
151    function welcomeVisitors() {
152      visitors += 50;
153      console.log(`   Greeting new visitors. Now: ${visitors}`);
154    }
155
156    welcomeVisitors();
157  }
158
159  function closePark() {
160    console.log(`\nClosing ${parkName}`);
161    console.log(`   Today's visitors: ${visitors}`);
162  }
163
164  openPark();
165  closePark();
166}
167
168managePark();
169
170// ===========================================
171// 7. SHADOWING
172// ===========================================
173console.log("\n=== 7. Shadowing (variable shadowing) ===");
174
175const dinosaur = "T-Rex (global)";
176
177function checkDinosaur() {
178  const dinosaur = "Velociraptor (function)"; // Shadows global
179  console.log(`In function: ${dinosaur}`);
180
181  if (true) {
182    const dinosaur = "Triceratops (block)"; // Shadows function
183    console.log(`   In block: ${dinosaur}`);
184  }
185
186  console.log(`   After block: ${dinosaur}`);
187}
188
189console.log(`Globally: ${dinosaur}`);
190checkDinosaur();
191console.log(`Again globally: ${dinosaur}`);
192
193// ===========================================
194// 8. LEXICAL SCOPE
195// ===========================================
196console.log("\n=== 8. Lexical scope ===");
197
198const habitat = "Tropical forest";
199
200function outerFunction() {
201  const temperature = 28;
202
203  function innerFunction() {
204    const humidity = 80;
205
206    // Access to all variables from parent scopes
207    console.log(`Environment: ${habitat}`);
208    console.log(`   Temperature: ${temperature}Β°C`);
209    console.log(`   Humidity: ${humidity}%`);
210  }
211
212  innerFunction();
213}
214
215outerFunction();
216
217// ===========================================
218// 9. PRACTICAL EXAMPLE - FEEDING SYSTEM
219// ===========================================
220console.log("\n=== 9. Feeding system - practical example ===");
221
222function createFeedingSystem() {
223  const feedingSchedule = [];
224  let lastFeedingTime = null;
225
226  return {
227    scheduleFeed: function(dinoName, time) {
228      const entry = { dino: dinoName, time: time, completed: false };
229      feedingSchedule.push(entry);
230      console.log(`Feeding scheduled for: ${dinoName} at ${time}`);
231    },
232
233    completeFeed: function(dinoName) {
234      const entry = feedingSchedule.find(e => e.dino === dinoName && !e.completed);
235      if (entry) {
236        entry.completed = true;
237        lastFeedingTime = new Date().toLocaleTimeString();
238        console.log(`Fed ${dinoName} at ${lastFeedingTime}`);
239      } else {
240        console.log(`No feeding scheduled for ${dinoName}`);
241      }
242    },
243
244    getReport: function() {
245      console.log("\nFEEDING REPORT:");
246      console.log(`   Last feeding: ${lastFeedingTime || "None"}`);
247      console.log(`   Scheduled feedings: ${feedingSchedule.length}`);
248
249      const pending = feedingSchedule.filter(e => !e.completed);
250      if (pending.length > 0) {
251        console.log("\n   Pending:");
252        pending.forEach(e => {
253          console.log(`     - ${e.dino} at ${e.time}`);
254        });
255      }
256    }
257  };
258}
259
260const feedingSystem = createFeedingSystem();
261
262feedingSystem.scheduleFeed("T-Rex", "08:00");
263feedingSystem.scheduleFeed("Velociraptor", "10:00");
264feedingSystem.scheduleFeed("Triceratops", "12:00");
265
266feedingSystem.completeFeed("T-Rex");
267feedingSystem.completeFeed("Velociraptor");
268
269feedingSystem.getReport();
270
271// ===========================================
272// 10. BEST PRACTICES
273// ===========================================
274console.log("\n=== 10. Best practices ===");
275
276// GOOD: Use const for values that don't change
277const MAX_DINOSAURS = 50;
278console.log(`Maximum number of dinosaurs: ${MAX_DINOSAURS}`);
279
280// GOOD: Use let for changeable values
281let currentDinoCount = 15;
282currentDinoCount += 5;
283console.log(`Current count: ${currentDinoCount}`);
284
285// BAD: Avoid var (old way)
286// var oldStyle = "Not recommended";
287
288// GOOD: Limit variable scope to minimum
289function goodScope() {
290  const onlyHere = "Available only here";
291  return onlyHere;
292}
293
294console.log("\nCongratulations! You've mastered variable scope!");

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. Which variable declaration is best for a constant dinosaur species value?

  2. 2. What will the following code return: String(65000000)?

  3. 3. Dr. Wu is organizing data in the lab. Which data-to-JavaScript-type match is INCORRECT?

Hands-on tasks in the game

  • Code editor

    Form data is always text: const inputWeight = '6200', const inputLength = '9.5' and const inputTeeth = '800'. Convert them to numbers: numWeight with Number(), numLength with parseFloat() and numTeeth with parseInt(). Then store numWeight back as text in weightText with String(). Display all four values in the console together with their type (typeof).

Useful articles