Interview Importance: π΄ Critical β Scope is fundamental to JavaScript. Questions about scope, hoisting, and closures appear in 95% of technical interviews and are essential for debugging complex issues
Scope is the set of rules that determines how and where variables can be accessed in your code. It defines the visibility and lifetime of variables and functions, preventing naming conflicts and managing memory efficiently.
+-----------------------------------------------------------+
| GLOBAL SCOPE |
| +-----------------------------------------------------+ |
| | FUNCTION SCOPE | |
| | +-----------------------------------------------+ | |
| | | BLOCK SCOPE | | |
| | | | | |
| | | Variables here can access: | | |
| | | [Y] Own variables | | |
| | | [Y] Parent function variables | | |
| | | [Y] Global variables | | |
| | | [X] Sibling blocks | | |
| | +-----------------------------------------------+ | |
| +-----------------------------------------------------+ |
+-----------------------------------------------------------+
Direction: Inner -> Outer (never reverse!)
Think of scope like a house with different rooms:
+------------------------------------------------------------+
| GLOBAL (House) |
| |
| FUNCTION (Living Room) |
| +------------------------------------------------------+ |
| | BLOCK (Kitchen) | |
| | | |
| | - Kitchen items (let, const) stay in kitchen | |
| | - Living room items (var) accessible from kitchen | |
| | - House items (global) accessible everywhere | |
| | - Neighbor's rooms (other functions) = PRIVATE! | |
| +------------------------------------------------------+ |
+------------------------------------------------------------+
// Global scope - accessible everywhere const globalVar = 'I am global'; function parentFunction() { // Function scope - accessible inside this function const parentVar = 'I am in parent function'; if (true) { // Block scope - accessible only inside this block const blockVar = 'I am in block'; console.log(globalVar); // β Accessible console.log(parentVar); // β Accessible console.log(blockVar); // β Accessible } console.log(globalVar); // β Accessible console.log(parentVar); // β Accessible // console.log(blockVar); // β ReferenceError: blockVar is not defined } parentFunction(); console.log(globalVar); // β Accessible // console.log(parentVar); // β ReferenceError: parentVar is not defined // Note: The lines above are commented out because they would throw errors // and crash the program before reaching subsequent lines
| Problem | Scope Solution | Example |
|---|---|---|
| Variable conflicts | Encapsulation prevents naming collisions | Library development |
| Memory management | Variables get garbage collected when out of scope | Performance optimization |
| Code organization | Logical grouping of related functionality | Module patterns |
| Security | Hide implementation details | API design |
| Debugging | Clear boundaries make issues easier to trace | Error handling |
| Maintainability | Limited visibility reduces side effects | Large applications |
+-----------------------------------------------------------+
| SCOPE BENEFITS |
+-----------------------------------------------------------+
| [Y] Prevents naming conflicts |
| [Y] Enables proper memory management |
| [Y] Provides code organization |
| [Y] Enhances security through encapsulation |
| [Y] Improves debugging and maintainability |
| [Y] Foundation for closures and advanced patterns |
+-----------------------------------------------------------+
// 1. Global Scope - Declared outside any function let globalVar = 'Global'; function demonstrateScopes() { // 2. Function Scope - Function-wide (var, function declarations) var functionVar = 'Function'; var anotherVar = 'Also Function'; if (true) { // 3. Block Scope - Limited to {} (let, const) let blockVar = 'Block'; const constVar = 'Constant Block'; // 4. Scope Chain - Can access outer scopes console.log(globalVar); // β Global console.log(functionVar); // β Function scope console.log(blockVar); // β Block scope console.log(constVar); // β Block scope } // These work: console.log(globalVar); // β Global console.log(functionVar); // β Function scope // These fail: // console.log(blockVar); // β ReferenceError // console.log(constVar); // β ReferenceError } // Global access: console.log(globalVar); // β Global // console.log(functionVar); // β ReferenceError
CODE:
function outer() {
let outerVar = 'outer';
function inner() {
let innerVar = 'inner';
console.log(outerVar);
}
inner();
}
outer();
EXECUTION TRACE:
===============================================================
Step 1: Global Execution Context Created
-----------------------------------------------------------
Variable Environment: { outer: function }
Scope Chain: [Global]
Step 2: outer() is called
-----------------------------------------------------------
Create outer Execution Context:
Variable Environment: { outerVar: 'outer', inner: function }
Scope Chain: [outer, Global]
[[Scope]]: points to Global context
Step 3: inner() is defined within outer()
-----------------------------------------------------------
inner.[[Scope]] = [outer context, Global context]
This creates the closure chain!
Step 4: inner() is called
-----------------------------------------------------------
Create inner Execution Context:
Variable Environment: { innerVar: 'inner' }
Scope Chain: [inner, outer, Global]
Step 5: console.log(outerVar) execution
-----------------------------------------------------------
Variable lookup process:
1. Check inner scope: 'outerVar' not found
2. Check outer scope: 'outerVar' found = 'outer' [Y]
3. Stop lookup, use 'outer'
Console output: "outer"
Step 6: inner() completes
-----------------------------------------------------------
Inner execution context destroyed
innerVar is garbage collected
Step 7: outer() completes
-----------------------------------------------------------
Outer execution context destroyed
outerVar is garbage collected
FINAL STATE:
===============================================================
All local variables garbage collected
Global context remains with outer function definition
// JavaScript uses LEXICAL scope (static scope) function lexicalScope() { const message = 'Lexical scope!'; function inner() { console.log(message); // Looks up where function was written } return inner; } const func = lexicalScope(); func(); // "Lexical scope!" - determined at write time // If JS had DYNAMIC scope (hypothetical): // The value would depend on where the function is called, not where it's defined
// Function declarations are fully hoisted console.log(hoistedFunction()); // "I work!" β function hoistedFunction() { return 'I work!'; } // Variable declarations are hoisted, but assignments are not console.log(hoistedVar); // undefined (not ReferenceError) var hoistedVar = 'I am hoisted'; // let and const are hoisted but in Temporal Dead Zone // console.log(letVar); // ReferenceError: Cannot access before initialization let letVar = 'I am block-scoped'; // Function scope with var function testVar() { console.log(varValue); // undefined (hoisted) if (false) { var varValue = 'I exist everywhere in function'; } console.log(varValue); // undefined } // Block scope with let function testLet() { // console.log(letValue); // ReferenceError if (true) { let letValue = 'I only exist in this block'; } // console.log(letValue); // ReferenceError }
// Level 1: Global Scope const globalVar = 'Global'; function level2() { // Level 2: Function Scope const level2Var = 'Level 2'; function level3() { // Level 3: Function Scope const level3Var = 'Level 3'; function level4() { // Level 4: Function Scope const level4Var = 'Level 4'; // Access chain: console.log(level4Var); // β Own scope console.log(level3Var); // β Parent scope console.log(level2Var); // β Grandparent scope console.log(globalVar); // β Global scope } level4(); } level3(); } level2();
/** * Creates a scope-safe utility for managing variables with different visibility */ function createScopeManager() { // Private scope - only accessible within this closure const privateData = new Map(); const protectedData = new Map(); let accessLog = []; // Public API - accessible outside return { /** * Add private data (only accessible via methods) */ setPrivate(key, value) { privateData.set(key, value); accessLog.push(`Private set: ${key} at ${new Date().toISOString()}`); }, /** * Get private data (controlled access) */ getPrivate(key) { const value = privateData.get(key); accessLog.push(`Private accessed: ${key} at ${new Date().toISOString()}`); return value; }, /** * Add protected data (readable from outside) */ setProtected(key, value) { protectedData.set(key, value); }, /** * Get protected data (public access) */ getProtected(key) { return protectedData.get(key); }, /** * Clear all data (admin function) */ clearAll() { privateData.clear(); protectedData.clear(); accessLog = []; }, /** * Get access log (audit trail) */ getAccessLog() { return [...accessLog]; }, /** * Get scope info (for debugging) */ getScopeInfo() { return { privateKeys: privateData.size, protectedKeys: protectedData.size, logEntries: accessLog.length }; } }; } // Usage const userManager = createScopeManager(); userManager.setPrivate('apiKey', 'secret-key-123'); userManager.setPrivate('password', 'hashed-password'); userManager.setProtected('username', 'john_doe'); console.log(userManager.getProtected('username')); // 'john_doe' console.log(userManager.getPrivate('apiKey')); // 'secret-key-123' console.log(userManager.privateData); // undefined - truly private! // Debugging console.log(userManager.getScopeInfo()); console.log(userManager.getAccessLog());
/** * Simulates dynamic scoping for specific use cases * Note: This is an educational example, not recommended for production */ function createDynamicScope() { const dynamicContext = new WeakMap(); return { /** * Execute function with dynamic context */ withContext(context, fn) { // Store current context const previousContext = dynamicContext.get(fn) || {}; // Merge new context const mergedContext = { ...previousContext, ...context }; dynamicContext.set(fn, mergedContext); try { return fn(); } finally { // Restore previous context if (Object.keys(previousContext).length === 0) { dynamicContext.delete(fn); } else { dynamicContext.set(fn, previousContext); } } }, /** * Get dynamic variable */ getDynamic(fn, key) { const context = dynamicContext.get(fn) || {}; return context[key]; } }; } // Usage example const dynamicScope = createDynamicScope(); function showMessage() { const message = dynamicScope.getDynamic(showMessage, 'message') || 'Default message'; const user = dynamicScope.getDynamic(showMessage, 'user') || 'Anonymous'; console.log(`${user}: ${message}`); } // Normal execution showMessage(); // "Anonymous: Default message" // With dynamic context dynamicScope.withContext( { message: 'Hello from dynamic scope!', user: 'Alice' }, showMessage ); // "Alice: Hello from dynamic scope!" // Back to normal showMessage(); // "Anonymous: Default message"
// Self-executing function creates private scope const ShoppingCart = (function() { // Private variables - only accessible within this closure let cart = []; let totalPrice = 0; let discountCode = null; // Private functions function calculateTotal() { return cart.reduce((sum, item) => sum + (item.price * item.quantity), 0); } function validateItem(item) { return item.id && item.price >= 0 && item.quantity > 0; } function applyDiscount(total) { if (!discountCode) return total; const discounts = { 'SAVE10': 0.1, 'SAVE20': 0.2, 'HALF': 0.5 }; return total * (1 - (discounts[discountCode] || 0)); } // Public API - returned object return { /** * Add item to cart */ addItem(item) { if (!validateItem(item)) { throw new Error('Invalid item'); } const existingItem = cart.find(cartItem => cartItem.id === item.id); if (existingItem) { existingItem.quantity += item.quantity; } else { cart.push({ ...item }); } totalPrice = calculateTotal(); return this; }, /** * Remove item from cart */ removeItem(itemId) { cart = cart.filter(item => item.id !== itemId); totalPrice = calculateTotal(); return this; }, /** * Get cart items (read-only copy) */ getItems() { return [...cart]; // Return copy, prevent mutation }, /** * Get total price with discounts */ getTotal() { return applyDiscount(totalPrice); }, /** * Apply discount code */ applyDiscount(code) { discountCode = code.toUpperCase(); return this; }, /** * Clear cart */ clear() { cart = []; totalPrice = 0; discountCode = null; return this; }, /** * Get cart statistics */ getStats() { return { itemCount: cart.reduce((sum, item) => sum + item.quantity, 0), uniqueItems: cart.length, subtotal: totalPrice, discount: discountCode, finalTotal: this.getTotal() }; } }; })(); // Usage ShoppingCart .addItem({ id: 1, name: 'Book', price: 20, quantity: 2 }) .addItem({ id: 2, name: 'Pen', price: 5, quantity: 3 }) .applyDiscount('SAVE10'); console.log(ShoppingCart.getItems()); console.log(ShoppingCart.getTotal()); // Price with discount console.log(ShoppingCart.getStats()); // Private access attempts: console.log(ShoppingCart.cart); // undefined console.log(ShoppingCart.totalPrice); // undefined console.log(ShoppingCart.discountCode); // undefined
/** * Scope-safe event handler factory */ function createEventHandlerFactory() { const handlers = new Map(); let eventCounter = 0; return { /** * Create scoped event handler */ createHandler(element, eventType, callback, options = {}) { const handlerId = ++eventCounter; // Create closure with captured element and options const scopedHandler = function(event) { // Preserve the intended 'this' context const context = options.context || element; try { // Call callback with controlled scope return callback.call(context, event, { element, eventType, handlerId, timestamp: Date.now() }); } catch (error) { console.error(`Handler ${handlerId} error:`, error); if (options.onError) { options.onError(error, event); } } }; // Store handler reference for potential removal handlers.set(handlerId, { element, eventType, handler: scopedHandler, options }); // Attach listener element.addEventListener(eventType, scopedHandler, options); return { id: handlerId, remove: () => this.removeHandler(handlerId) }; }, /** * Remove specific handler */ removeHandler(handlerId) { const handlerInfo = handlers.get(handlerId); if (handlerInfo) { const { element, eventType, handler } = handlerInfo; element.removeEventListener(eventType, handler); handlers.delete(handlerId); return true; } return false; }, /** * Remove all handlers */ removeAllHandlers() { for (const [handlerId, handlerInfo] of handlers) { const { element, eventType, handler } = handlerInfo; element.removeEventListener(eventType, handler); } handlers.clear(); }, /** * Get handler statistics */ getStats() { const stats = {}; for (const [, { eventType }] of handlers) { stats[eventType] = (stats[eventType] || 0) + 1; } return stats; } }; } // Usage const eventFactory = createEventHandlerFactory(); const button = document.querySelector('button'); const input = document.querySelector('input'); // Create scoped handlers const buttonHandler = eventFactory.createHandler(button, 'click', function(event, meta) { console.log('Button clicked!', meta); console.log('this.context:', this); }, { context: { name: 'ButtonHandler' } }); const inputHandler = eventFactory.createHandler(input, 'input', function(event, meta) { console.log(`Input changed: ${event.target.value}`); console.log('Handler metadata:', meta); }); console.log('Handler stats:', eventFactory.getStats()); // Cleanup when needed // buttonHandler.remove(); // eventFactory.removeAllHandlers();
| Feature | var | let | const |
|---|---|---|---|
| Scope | Function | Block | Block |
| Hoisting | Yes (initialized as undefined) | Yes (TDZ) | Yes (TDZ) |
| Reassignment | β | β | β |
| Redeclaration | β | β | β |
| Global Object Property | β (window.varName) | β | β |
| Use in Loops | β (creates closure issues) | β | β |
// var - Function scoped function varExample() { var x = 1; if (true) { var x = 2; // Same variable! console.log(x); // 2 } console.log(x); // 2 (not 1!) } // let - Block scoped function letExample() { let x = 1; if (true) { let x = 2; // Different variable! console.log(x); // 2 } console.log(x); // 1 (unchanged!) } // const - Block scoped, immutable reference function constExample() { const x = 1; // x = 2; // TypeError: Assignment to constant variable if (true) { const x = 2; // Different variable! console.log(x); // 2 } console.log(x); // 1 (unchanged!) // Object properties can still change const obj = { name: 'John' }; obj.name = 'Jane'; // β Works! // obj = {}; // β TypeError }
// In browser: var globalVar = 'I am on window'; // Becomes window.globalVar let globalLet = 'I am not on window'; // Stays in script scope const globalConst = 'I am not on window'; // Stays in script scope console.log(window.globalVar); // 'I am on window' console.log(window.globalLet); // undefined console.log(window.globalConst); // undefined // In Node.js (REPL only): var nodeVar = 'I am on global'; // Becomes global.nodeVar (REPL only!) let nodeLet = 'I am not on global'; // Stays in module scope // In Node.js modules (.js files): // All top-level variables are module-scoped, NOT global // var, let, and const all stay within the module var moduleVar = 'Module scoped'; // NOT on global object let moduleLet = 'Module scoped'; // NOT on global object
ES modules (import/export) create their own scope, which is crucial for modern JavaScript development:
// ----------------------------------------------------------- // utils.js - Module scope // ----------------------------------------------------------- // Private to this module (not exported) const SECRET_KEY = 'abc123'; let requestCount = 0; // Exported - accessible to importers export const API_URL = 'https://api.example.com'; export function fetchData(endpoint) { requestCount++; // Can access module-private variables return fetch(`${API_URL}/${endpoint}`, { headers: { 'X-Key': SECRET_KEY } }); } // Export function to read private state (controlled access) export function getRequestCount() { return requestCount; } // ----------------------------------------------------------- // app.js - Importing module // ----------------------------------------------------------- import { API_URL, fetchData, getRequestCount } from './utils.js'; console.log(API_URL); // β 'https://api.example.com' console.log(getRequestCount()); // β 0 // These don't work - not exported: // console.log(SECRET_KEY); // β ReferenceError // console.log(requestCount); // β ReferenceError
Module Scope Benefits:
+-----------------------------------------------------------+
| ES MODULE SCOPE |
+-----------------------------------------------------------+
| [Y] Automatic strict mode |
| [Y] Top-level variables are module-private by default |
| [Y] Explicit exports control public API |
| [Y] No global namespace pollution |
| [Y] Static analysis enables tree-shaking |
| [Y] Each module runs once (singleton pattern built-in) |
+-----------------------------------------------------------+
var a = 1; function test() { console.log(a); var a = 2; console.log(a); } test(); console.log(a);
Answer:
undefined
2
1
Explanation:
1. Due to hoisting, 'var a' inside test() is moved to top of function
2. It's initialized as undefined, shadowing the global 'a'
3. First console.log sees the local 'a' (undefined)
4. Assignment makes local 'a' = 2
5. Second console.log sees local 'a' (2)
6. After function ends, global 'a' is unchanged (1)
Visual:
+-----------------------------------------------------------+
| Global Scope: var a = 1 |
| |
| test() Function Scope (after hoisting): |
| var a = undefined; // <-- Shadowing global |
| console.log(a); // --> undefined |
| a = 2; // Assignment |
| console.log(a); // --> 2 |
+-----------------------------------------------------------+
// Loop 1 for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // Loop 2 for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); }
Answer:
Loop 1 output: 3, 3, 3
Loop 2 output: 0, 1, 2
Why?
Loop 1 (var):
+-----------------------------------------------------------+
| var is function-scoped, not block-scoped |
| Single 'i' variable shared across all iterations |
| When setTimeout runs (100ms later), i = 3 (loop done) |
| All callbacks reference the same 'i' variable |
+-----------------------------------------------------------+
Loop 2 (let):
+-----------------------------------------------------------+
| let is block-scoped, creates new binding each iteration |
| Each setTimeout callback captures its own 'i' value |
| First callback has i=0, second i=1, third i=2 |
+-----------------------------------------------------------+
console.log(typeof myVar); // undefined console.log(typeof myLet); // ReferenceError var myVar = 'var variable'; let myLet = 'let variable';
Answer:
Temporal Dead Zone (TDZ) is the period between:
1. When a block-scope variable (let/const) is hoisted
2. When it's actually declared in the code
Timeline:
+-----------------------------------------------------------+
| 1. JavaScript enters scope |
| 2. 'let myLet' is hoisted (in TDZ) |
| 3. typeof myLet --> ReferenceError (TDZ active) |
| 4. let myLet = '...' declaration reached |
| 5. TDZ ends, myLet becomes accessible |
+-----------------------------------------------------------+
var doesn't have TDZ - it's initialized as undefined during hoisting.
// Using closures: function createCounter() { let count = 0; // Private via closure return { increment: () => ++count, get: () => count }; } // Using ES2022 private fields: class Counter { #count = 0; // Private field increment() { return ++this.#count; } get() { return this.#count; } }
Answer: There are two main approaches:
Closure-based privacy (pre-ES2022):
Private class fields (ES2022+):
Both provide true privacy, but closures are more flexible for factory patterns, while private fields work better with class inheritance.
(function() { var a = b = 3; })(); console.log(typeof a === 'undefined'); // ? console.log(typeof b === 'undefined'); // ?
Answer:
true
false
Explanation:
The statement `var a = b = 3;` is actually two operations:
1. `b = 3` - Assignment to undeclared variable -> becomes GLOBAL
2. `var a = b` - Declaration of local variable, assignment of global b
Breaking it down:
+-----------------------------------------------------------+
| (function() { |
| var a = b = 3; // <-- Right to left evaluation |
| // Equivalent to: |
| b = 3; // Global variable (no var/let/const) |
| var a = b; // Local variable inside IIFE |
| })(); |
| |
| // After IIFE executes: |
| // 'a' is local to IIFE --> destroyed |
| // 'b' is global --> accessible |
+-----------------------------------------------------------+
So:
typeof a === 'undefined' // true (a doesn't exist globally)
typeof b === 'undefined' // false (b exists globally as 3)
function Person(name) { if (!(this instanceof Person)) { return new Person(name); } this.name = name; }
Answer: A scope-safe constructor ensures it works correctly even without new:
function ScopeSafeConstructor(name) { // Check if called with 'new' if (!(this instanceof ScopeSafeConstructor)) { // If not, call with 'new' and return return new ScopeSafeConstructor(name); } // Now 'this' is correctly bound this.name = name; this.greet = function() { return `Hello, I'm ${this.name}`; }; } // Both work correctly: const obj1 = new ScopeSafeConstructor('John'); const obj2 = ScopeSafeConstructor('Jane'); // Without 'new' still works! console.log(obj1.name); // 'John' console.log(obj2.name); // 'Jane'
This pattern prevents accidental constructor calls that would bind this to the global object.
// β BAD - Unexpected undefined function testHoisting() { console.log(myVar); // undefined, not ReferenceError var myVar = 'Hello'; } // β GOOD - Clear variable declaration function testHoistingFixed() { var myVar; console.log(myVar); // undefined (clearly visible) myVar = 'Hello'; } // β BETTER - Use let/const to avoid hoisting confusion function testHoistingBetter() { // console.log(myVar); // ReferenceError - clearly an error let myVar = 'Hello'; }
What Goes Wrong: var declarations are hoisted and initialized as undefined, leading to confusing behavior where variables appear to be accessible before declaration.
// β BAD - Accidentally creating globals function polluteScope() { for (i = 0; i < 5; i++) { // 'i' becomes global! console.log(i); } } polluteScope(); console.log(i); // 5 - leaked to global scope! // β GOOD - Proper variable declaration function cleanScope() { for (let i = 0; i < 5; i++) { // 'i' is block-scoped console.log(i); } } cleanScope(); // console.log(i); // ReferenceError - properly scoped
What Goes Wrong: Undeclared variables become global properties, polluting the global namespace and potentially causing conflicts.
// β BAD - All handlers get the same 'id' function attachBadHandlers() { for (var id = 0; id < 3; id++) { document.getElementById(`btn-${id}`).onclick = function() { console.log(`Button ${id} clicked`); // Always "Button 3" }; } } // β GOOD - Use let for proper closure function attachGoodHandlers() { for (let id = 0; id < 3; id++) { // Block-scoped binding document.getElementById(`btn-${id}`).onclick = function() { console.log(`Button ${id} clicked`); // Correct: 0, 1, 2 }; } } // β ALSO GOOD - Use IIFE for var function attachHandlersWithIIFE() { for (var id = 0; id < 3; id++) { (function(capturedId) { document.getElementById(`btn-${capturedId}`).onclick = function() { console.log(`Button ${capturedId} clicked`); }; })(id); // Capture current value } }
What Goes Wrong: With var, all closures share the same variable reference. By the time handlers execute, the loop has finished and the variable has its final value.
// β BAD - 'this' context lost const obj = { name: 'Object', methods: { greet: function() { setTimeout(function() { console.log(`Hello, ${this.name}`); // 'this' is undefined/window }, 100); } } }; // β GOOD - Preserve 'this' with arrow function const objFixed = { name: 'Object', methods: { greet: function() { setTimeout(() => { console.log(`Hello, ${this.name}`); // 'this' preserved }, 100); } } }; // β ALSO GOOD - Capture 'this' in closure const objFixed2 = { name: 'Object', methods: { greet: function() { const self = this; setTimeout(function() { console.log(`Hello, ${self.name}`); // Uses captured 'self' }, 100); } } };
What Goes Wrong: Regular functions create their own this context. Arrow functions inherit this from the enclosing scope.
| Operation | Time | Space | Explanation |
|---|---|---|---|
| Variable access (same scope) | O(1) | O(1) | Direct lookup |
| Scope chain traversal | O(k) | O(1) | k = scope depth |
| Variable declaration | O(1) | O(1) | Memory allocation |
| Closure creation | O(1) | O(n) | n = closed variables |
| Garbage collection | O(g) | O(1) | g = objects to collect |
+-----------------------------------------------------------+
| MEMORY ALLOCATION |
+-----------------------------------------------------------+
| |
| Each execution context contains: |
| +-----------------------------------------------------+ |
| | Variable Environment: | |
| | - Local variables (var, let, const) | |
| | - Function arguments | |
| | - 'this' binding | |
| | | |
| | Scope Chain: | |
| | - [[Scope]] -> Parent context -> Grandparent... | |
| +-----------------------------------------------------+ |
| |
| Memory grows with: |
| - Nested function calls (call stack) |
| - Closures retaining outer scopes |
| - Long-lived objects in global scope |
| |
| Memory freed when: |
| - Function calls return (stack unwinding) |
| - No more references to objects exist |
| - Closures are garbage collected |
+-----------------------------------------------------------+
| Concept | Description | Key Points |
|---|---|---|
| Global Scope | Top-level scope accessible everywhere | Avoid pollution, use modules |
| Function Scope | Function-wide scope (var, declarations) | Hoisting applies to var |
| Block Scope | Limited to {} blocks (let, const) | Temporal Dead Zone |
| Scope Chain | Nested lookup mechanism | Inner -> Outer direction |
| Lexical Scope | Scope determined at write-time | JavaScript's scoping model |
| Hoisting | Declarations moved to top | var vs let/const behavior |
JavaScript Uses Lexical Scope β Scope is determined by where code is written, not where it's executed
Block Scope is Safer β Prefer let and const over var to avoid hoisting confusion and global pollution
Closures Create Scope Chains β Functions retain access to outer scope variables, enabling powerful patterns but potential memory issues
Watch the Global Namespace β Undeclared variables become global; use strict mode and proper declarations
Understanding Scope is Foundation β Scope knowledge is essential for closures, this binding, hoisting, and advanced JavaScript patterns
Test your understanding with 3 quick questions