JavaScript and TypeScript course Β· Module 2: Functions, Arrays and Objects
Variable Scope (Global, Function, Block)
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:
- Global scope
- Function scope
- 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:
varhas function scope, but not block scopeletandconsthave 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:
var:- Has function or global scope, but not block scope
- Can be redeclared multiple times
- Can be modified
- Is hoisted with the value
undefinedassigned to it
let:- Has block scope
- Cannot be redeclared in the same scope
- Can be modified
- Is hoisted without a value being assigned (temporal dead zone)
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 accessibleGood Practice Rules
Avoid global variables - just like in a real park, the more public areas there are, the harder they are to monitor and secure.
Prefer
constoverlet, andletovervar- most variables should not change their values, and if they do, they should have the smallest possible scope.Use block scope - limit the visibility of variables to the smallest possible scope.
Apply the module pattern - encapsulate variables and functions in modules, exposing only the necessary API.
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. Which variable declaration is best for a constant dinosaur species value?
2. What will the following code return: String(65000000)?
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).