This lesson is important because JavaScript variable scope behaves differently enough from Ruby to cause bugs and int. confusion.
The four things to understand:
letconstscopehoisting
let, const, Scope and Hoisting
1. let vs const
Start with the simplest rule:
const name = "Abhilash";let age = 40;
Use const when the variable should not be reassigned:
const name = "Abhilash";// name = "John"; ❌
Use let when reassignment is required:
let age = 40;age = 41; // ✅
Important: const does NOT make the value immutable
This is a very important JavaScript int. question.
const users = [];users.push("John"); // ✅
The array can be modified.
What cannot happen is:
users = ["Jane"]; // ❌
Why?
Because const prevents reassignment of the variable, not mutation of the object.
Think:
const users | v [ "John" ]
You cannot make users point somewhere else:
users ─────X────> [ "Jane" ]
But you can modify the existing array:
users | v["John", "Jane"]
The same applies to objects:
const user = { name: "John"};user.name = "Jane"; // ✅
But:
user = {}; // ❌
Int. answer
constprevents reassignment, but it does not make objects or arrays immutable.
2. What is scope?
Scope simply means:
Where can this variable be accessed?
Example:
const name = "Abhilash";function greet() { console.log(name);}greet();
name is available inside greet() because functions can access variables from their outer scope.
But:
function greet() { const message = "Hello";}console.log(message); // ❌
message exists only inside the function.
Visualize:
Global Scope│├── name│└── greet() │ └── message
greet() can see name.
The outside cannot see message.
3. Block scope
This is where let and const become important.
A block is anything inside { }, such as:
if (...){}
or:
for (...){}
Example:
if (true) { const message = "Hello"; console.log(message); // ✅}console.log(message); // ❌
message is block scoped.
Same with let:
if (true) { let age = 40;}console.log(age); // ❌
4. Why var is different
Historically JavaScript used:
var name = "John";
var does not have block scope.
Example:
if (true) { var name = "John";}console.log(name); // John
That surprises many developers.
Compare:
if (true) { let name = "John";}console.log(name); // ❌
This is one major reason modern JavaScript prefers let and const.
5. Ruby comparison
You may initially think:
if true name = "John"endputs name
Ruby’s local-variable behavior is different from JavaScript’s block scoping rules.
For JavaScript, remember:
const + let ↓block scoped
and:
var ↓function scoped
You don’t need to use var in modern code, but you must understand it when reading legacy JavaScript.
6. Nested scope
Scopes can be nested.
const country = "India";function outer() { const state = "Kerala"; function inner() { const city = "Kochi"; console.log(country); console.log(state); console.log(city); } inner();}
inner() can access:
countrystatecity
because it can look outward through its scope chain.
But outer() cannot access city.
Visualize:
Global│├── country│└── outer │ ├── state │ └── inner │ └── city
JavaScript searches for variables from the current scope outward.
This is called the scope chain.
7. A very important example
Look carefully:
const name = "Abhilash";function greet() { const message = `Hello ${name}`; console.log(message);}greet();
Inside greet():
name
is not declared locally.
JavaScript looks outward:
greet scope ↓global scope ↓find name
This behavior becomes extremely important when we learn closures.
8. What is hoisting?
Now we get to a classic int. topic.
JavaScript processes declarations before executing the code in the current scope.
This behavior is commonly called hoisting.
But don’t interpret it as JavaScript literally moving your code to the top. That’s a useful mental model, but the actual mechanics are more nuanced.
Start with a function declaration:
greet();function greet() { console.log("Hello");}
This works.
Why?
Function declarations are available before their textual position.
9. var and hoisting
Consider:
console.log(name);var name = "John";
You might expect an error.
Instead:
undefined
A useful mental model is:
var name;console.log(name);name = "John";
The declaration is available, but the assignment happens later.
10. let and const are different
Now:
console.log(name);let name = "John";
This produces:
ReferenceError
And:
console.log(name);const name = "John";
also produces:
ReferenceError
This is often explained using the Temporal Dead Zone (TDZ).
11. Temporal Dead Zone
For let and const, the variable exists in the scope before its declaration is executed, but you cannot access it before that point.
Example:
console.log(age); // ❌let age = 40;
The area between entering the scope and reaching the declaration is called the:
Temporal Dead Zone
You don’t need to memorize the implementation details yet. The int.-level mental model is:
var↓hoisted + initialized as undefinedlet / const↓hoisted but inaccessible until declaration is reached
12. Function declarations vs function expressions
This becomes important when we get to callbacks.
This works:
greet();function greet() { console.log("Hello");}
But this does not:
greet();const greet = function() { console.log("Hello");};
And similarly:
greet();const greet = () => { console.log("Hello");};
The second forms involve a const variable, so the TDZ applies.
13. A common int. trap
What does this print?
var x = 10;if (true) { var x = 20;}console.log(x);
Answer:
20
Because var is function scoped, not block scoped.
Now:
let x = 10;if (true) { let x = 20;}console.log(x);
Answer:
10
The inner x belongs to the block.
Visualize:
let x = 10│├── if block│ └── let x = 20│└── outside -> x is still 10
14. Shadowing
JavaScript allows an inner scope to define a variable with the same name.
const name = "Abhilash";function test() { const name = "John"; console.log(name);}test();console.log(name);
Output:
JohnAbhilash
The inner variable shadows the outer one.
This is perfectly valid, although unnecessary shadowing can make code harder to read.
15. Why this matters in React
Consider:
function UserList() { const [users, setUsers] = useState([]); if (users.length === 0) { const message = "No users"; return <p>{message}</p>; } return ( <ul> ... </ul> );}
message exists only inside that if block.
React code frequently contains nested blocks, callbacks and functions, so understanding scope prevents many bugs.
16. Scope + callbacks = important later
Consider:
function createCounter() { let count = 0; return function() { count++; console.log(count); };}
Don’t worry about understanding every detail yet.
The interesting question is:
How can the returned function still access
countaftercreateCounter()has finished?
That question leads directly to:
closures.
And closures are one of the most important JavaScript concepts for React and Node.js.
Qn) Is it necessary to put semi colon in Javascript?
No, putting a semicolon (or colon) at the end of a line is not mandatory in ES6 (ECMAScript 2015) or modern JavaScript.
JavaScript uses a feature called Automatic Semicolon Insertion (ASI). This means the JavaScript engine automatically inserts semicolons where it thinks they are needed to run your code correctly.
⚠️ The Rare Exception
While you can safely omit semicolons 99% of the time, there are rare cases where leaving them out can break your code. This usually happens if a line starts with a bracket [ or parenthesis (.
Example of a bug without semicolons:
const user = "Alice"['a', 'b'].forEach(letter => console.log(letter))
The JavaScript engine reads this as a single continuous line: const user = "Alice"['a', 'b'].forEach(...), which throws an error.
Best Practice
Because of ASI, choosing to use semicolons is largely a matter of personal or team preference:
- With semicolons: Safer for beginners, prevents accidental ASI bugs, and follows traditional coding styles.
- Without semicolons: Keeps code looking clean, modern, and reduces visual clutter.
Most development teams use a tool like Prettier or ESLint to automatically format the code and handle semicolons for them.
Int. questions
Try answering these without looking back.
Q1
What is the difference between:
const user = {};
and:
let user = {};
Q2
Why does this work?
const users = [];users.push("John");
even though users is declared with const?
Q3
What is the output?
let x = 10;if (true) { let x = 20;}console.log(x);
Q4
What is the output?
var x = 10;if (true) { var x = 20;}console.log(x);
Q5
What happens here?
console.log(name);let name = "John";
Q6
Why does this work?
greet();function greet() { console.log("Hello");}
but this doesn’t?
greet();const greet = () => { console.log("Hello");};
Mini exercise
Predict the output before running:
const name = "Abhilash";function outer() { const name = "John"; if (true) { const name = "Jane"; console.log(name); } console.log(name);}outer();console.log(name);
Expected mental trace:
global name ↓outer name ↓if-block name
The most important thing from this lesson is to build the habit of asking:
Which scope does this variable belong to?
That question will help enormously when we reach closures, callbacks, useEffect, event handlers, and async JavaScript.
Next lesson: Functions in depth – function declarations, expressions, arrow functions, parameters, return values, callbacks, and higher-order functions.