Scope is the set of rules that determines where a variable can be accessed. When you declare a variable, it’s only visible within a certain region of your code. Try to use it outside that region and you’ll get an error.
Copy
function processOrder() { const orderId = "ORD-001"; console.log(orderId); // ✅ Works — same scope}processOrder();console.log(orderId); // ❌ ReferenceError: orderId is not defined
orderId exists inside processOrder and nowhere else. This is a good thing — it means variables don’t leak into places they shouldn’t be.
Python mental model: this is the same general idea as Python scope (LEGB: Local, Enclosing, Global, Built-in). The biggest difference you’ll feel in JavaScript is that let/const also create block scope for if/for/while.
Variables declared with const and let are block-scoped. They exist only within the nearest set of curly braces {}.
Copy
if (true) { const message = "Inside the block"; let count = 42; console.log(message); // ✅ "Inside the block" console.log(count); // ✅ 42}console.log(message); // ❌ ReferenceErrorconsole.log(count); // ❌ ReferenceError
Every if, for, while, and function body creates a new block scope:
Copy
for (let i = 0; i < 3; i++) { const item = `Item ${i}`; console.log(item); // ✅ Works inside the loop}console.log(i); // ❌ ReferenceErrorconsole.log(item); // ❌ ReferenceError
JavaScript
Python
Copy
// Block-scoped — each block is its own worldif (true) { const x = 10;}console.log(x); // ReferenceError
Copy
# Python doesn't have block scope for if/forif True: x = 10print(x) # 10 — still accessible!
This is a real difference from Python. In Python, if and for blocks don’t create their own scope — variables leak out. In JavaScript, they don’t.
If you’re used to Python, this will trip you up. Variables declared inside if, for, or while blocks in JavaScript are not accessible outside those blocks. This is actually safer — it prevents accidental name collisions.
Nested functions can access variables from their parent function:
Copy
function outer() { const outerVar = "I'm from outer"; function inner() { const innerVar = "I'm from inner"; console.log(outerVar); // ✅ Can see parent's variables console.log(innerVar); // ✅ Can see own variables } inner(); console.log(innerVar); // ❌ Can't see child's variables}
This is called lexical scoping — inner functions can look “up” to their parent’s scope, but parents can’t look “down” into children.
This part is very similar to Python: nested functions can read variables from outer functions. That’s the foundation for closures in both languages.
Variables declared outside any function or block are in the global scope. They’re accessible everywhere.
Copy
const API_URL = "http://localhost:8000"; // Globalfunction fetchData() { console.log(API_URL); // ✅ Works — global is visible everywhere}function sendData() { console.log(API_URL); // ✅ Also works}
Keep global variables to a minimum. Use them for true constants like API URLs and configuration values. Everything else should live in the smallest scope possible.
When JavaScript encounters a variable, it searches from the current scope outward:
Copy
const color = "blue"; // Globalfunction paintRoom() { const color = "green"; // Function scope — shadows the global if (true) { const color = "red"; // Block scope — shadows the function scope console.log(color); // "red" } console.log(color); // "green"}paintRoom();console.log(color); // "blue"
Each color is a different variable in a different scope. The inner-most scope wins. This is called variable shadowing.
Python mental model: same idea as name shadowing in nested scopes — a local variable named color hides an outer color. JavaScript’s scope chain and Python’s LEGB rule are solving the same problem.
Variable shadowing is legal but can be confusing. Avoid reusing the same variable name in nested scopes. ESLint can warn you about this with the no-shadow rule.
Accessing a block-scoped variable outside its block
Copy
// ❌ Wrong: Variable only exists inside the if blockif (userIsLoggedIn) { const welcomeMessage = `Welcome back, ${userName}!`;}console.log(welcomeMessage); // ReferenceError// ✅ Correct: Declare it in the outer scopelet welcomeMessage = "";if (userIsLoggedIn) { welcomeMessage = `Welcome back, ${userName}!`;}console.log(welcomeMessage); // Works
If you need a variable after a block ends, declare it before the block with let. You can assign the value inside the block.
Hoisting confusion with var
Copy
// var is hoisted and function-scoped — avoid itconsole.log(name); // undefined (not an error!)var name = "Sarah";// const and let are NOT hoisted the same wayconsole.log(name); // ReferenceError: Cannot access before initializationconst name = "Sarah";
var has confusing hoisting behavior — it exists before its declaration but with the value undefined. This is one of the main reasons the JavaScript community moved to const and let. Stick with const and let and you’ll never deal with hoisting issues.
You understand how scope works — variables live inside blocks and functions, and inner scopes can see outer scopes. This leads directly to one of JavaScript’s most useful features: closures.