JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
99: Let, Const, and Block Scoping Revisited
We've touched on let and const before, but as you start building larger applications, the subtle differences between block scoping and function scoping start to cause bugs that are a nightmare to debug. I've seen senior devs spend an hour chasing a bug that was solved by changing a single keyword.
Take a look at this snippet. I'm trying to create a simple notification system that logs a series of messages with a slight delay between each one.
const messages = ['Loading...', 'Fetching data...', 'Almost there...', 'Done!'];
for (var i = 0; i < messages.length; i++) {
setTimeout(() => {
console.log(messages[i]);
}, i * 1000);
}
// Expected: "Loading...", then "Fetching data...", etc.
// Actual: undefined, undefined, undefined, undefined (or an error)
The ghost of function scoping
If you ran this, you'd notice it doesn't print the messages in order. In fact, it likely prints undefined four times. Why? Because var does not care about blocks (like the for loop). It only cares about functions.
By the time the first setTimeout callback actually executes (after 0 milliseconds), the loop has already finished running. Because var i was hoisted to the top of the scope, there is only one i shared by every single timer. By the time the timers fire, i has already been incremented to 4. Since messages[4] doesn't exist, you get undefined.
Binding variables to the block
The fix is deceptively simple: change var to let.
const messages = ['Loading...', 'Fetching data...', 'Almost there...', 'Done!'];
for (let i = 0; i < messages.length; i++) {
setTimeout(() => {
console.log(messages[i]);
}, i * 1000);
}
Now it works perfectly. When you use let in a for loop header, JavaScript does something clever: it creates a new binding for i for every single iteration of the loop. Each setTimeout callback "closes over" its own unique version of i that never changes. I always tell people: unless you have a very specific, legacy reason to use var, just stop using it. There is almost no scenario in modern JS where var is the better choice.
Choosing between Let and Const in logic blocks
Once you've moved away from var, you're left with a choice: let or const? I tend to be aggressive with const. If a variable doesn't need to be reassigned, make it a constant.
Consider this pattern. I'm calculating a discounted price based on a user's membership status:
const calculatePrice = (price, isMember) => {
const discount = 0.1;
let finalPrice = price;
if (isMember) {
const memberBonus = 0.05;
finalPrice = price * (1 - (discount + memberBonus));
} else {
finalPrice = price * (1 - discount);
}
return finalPrice;
};
Notice that memberBonus only exists inside the if block. If I tried to access it outside that block, the engine would throw a ReferenceError. This is a good thing. It keeps the "mental surface area" of your function small. You don't have to wonder where memberBonus came from when you're looking at the return statement because it's physically impossible for it to be there.
A quick rule of thumb I use: Start with const. If you realize you need to change the value (like finalPrice above), change it to let. If you find yourself using let everywhere, you might be mutating state more than you need to, which is usually where the most elusive bugs hide.
📋 Practical Task
Fixing the User Permission Leak
You are working on a permissions system. The following code is intended to check if a user has "Admin" or "Editor" rights to access a specific folder. However, there is a scoping bug causing the accessLevel to be handled incorrectly across different blocks.
The Bug: The code uses var, which allows accessLevel to leak and be overwritten in ways the developer didn't intend. Additionally, some variables that should be constant are declared with let.
Your Goal:
- Refactor the code to use
letandconstappropriately. - Ensure that the
temporaryOverridevariable is strictly block-scoped to theifstatement so it cannot be accessed by the finalconsole.log. - Ensure the final output correctly identifies the user as "Admin" based on the logic.
const user = {
role: 'Admin',
id: 42
};
var accessLevel = 'Guest';
if (user.role === 'Admin') {
var accessLevel = 'Full Access';
var temporaryOverride = 'SuperUser_Mode';
console.log('Admin access granted.');
} else if (user.role === 'Editor') {
var accessLevel = 'Limited Access';
}
// This should print "Full Access",
// but temporaryOverride should NOT be accessible here.
console.log('Final Access Level: ' + accessLevel);
console.log('Override Status: ' + temporaryOverride);
There are no comments for now.