Feed
Log in to post, follow alumni, and join the conversation.
If a function finishes executing, its memory is wiped out. So how does this code remember the count? function createCounter() { let count = 0; return function() { count++; return count; }; } const counter = createCounter(); console.log(counter()); // 1 console.log(counter()); // 2 console.log(counter()); // 3 "createCounter()" executed and popped off the Call Stack on line 10. Yet every time we invoke "counter()", it accesses and increments "count" as if the parent function is still running. Is this a loop? No. This is one of the most powerful features in JavaScript: A Closure. Here is what happens under the hood: 1. What Actually Is a Closure? A closure is simply a function bundled together with references to its surrounding lexical environment. Whenever an inner function uses a variable from its outer parent and is returned or passed elsewhere, it doesn't leave those variables behind—it packs them into an invisible "backpack". 2. Where Is the Variable Stored? (Stack vs Heap) Under normal circumstances: • Local variables live on the Call Stack. Once a function returns, its frame is destroyed. With a Closure: • The V8 engine detects that the returned inner function still needs "count". • Instead of leaving "count" on the stack to be discarded, the engine allocates it into Heap Memory inside a special Closure object. • Even though the Call Stack frame is gone, the reference in Heap memory stays alive. 3. Why Doesn't the Garbage Collector Delete It? JavaScript's Garbage Collector works via "reachability". As long as the variable "counter" holds a reference to that inner function, the inner function holds a reference to the Heap record. Because the link is active, the engine keeps that memory intact. 4. Proof in the Browser DevTools Run this in your console: console.dir(counter); Expand the dropdown ➔ inspect [[Scopes]] ➔ open "Closure (createCounter)". You will literally see: "count: 3" sitting safely in heap storage. This exact mechanism is how private state, factory functions, and React's useState persist data across independent execution cycles without polluting global scope. Have you ever inspected a function's [[Scopes]] property inside DevTools to debug closure leaks?
Learning to Think Beyond Code
My web development journey at Saylani is about more than learning to write code. It's also about developing logical thinking, improving problem-solving skills, and learning how to use modern AI tools responsibly.
AI can support our learning, but understanding the concepts is what helps us grow.
Learn. Think. Build. Improve.
#Saylani #WebDevelopment #AI #LearningJourney #ProblemSolving
sehrishayaz25 just joined the alumni network
irshadfizza03 just joined the alumni network
tayyabaismail96 just joined the alumni network
Can you trace where JavaScript finds this variable?
const appName = "CoreCloud";
function parent() {
const user = "Ali";
function child() {
const role = "Admin";
console.log(${user} is an ${role} on ${appName});
}
child();
}
parent();
Output: "Ali is an Admin on CoreCloud"
The inner "child()" function accesses variables it never defined.
Yet, if you try accessing "role" outside, JavaScript throws an instant ReferenceError.
Why does variable access work seamlessly outward, but fails completely inward?
Here is what happens under the hood during Scope Resolution:
1. The Lexical Environment Rule
In JavaScript, "Lexical" simply refers to where your code is physically written in your file.
Whenever a function is declared, the engine gives it a hidden reference to its parent environment.
This link creates the "Scope Chain".
2. The Upward-Only Search Engine
When the engine executes "child()", it encounters three variables:
• Step 1 (role): Found immediately in its own Local Scope.
• Step 2 (user): Not in local scope. The engine climbs up to "parent()" and locates it.
• Step 3 (appName): Not in "parent()". The engine climbs further up to the Global Scope and finds it.
The Golden Rule of the Scope Chain:
Lookup only moves from Inside ➔ Outside.
An outer function can never peek inside an inner child's scope.
3. Variable Shadowing: The Hidden Mask
What happens if two scopes use the exact same variable name?
let count = 10;
function update() {
let count = 50;
console.log("Inside:", count);
}
update();
console.log("Outside:", count);
Output:
Inside: 50
Outside: 10
The inner variable "shadows" (masks) the outer one. It temporarily hides the outer reference without overwriting or mutating it.
4. The Interview Trap: Illegal Shadowing
Shadowing works safely with "let" and "const", but introduces breaking errors with "var":
let a = 10;
{
var a = 20; // Uncaught SyntaxError: Identifier 'a' has already been declared
}
Why? Because "var" is not block-scoped. It attempts to leak out of the curly braces into the outer scope where "let a" already exists, causing an instant compilation error.
Mastering the Scope Chain is the fundamental prerequisite to understanding Closures, Lexical Binding, and state encapsulation.
Did you know why illegal shadowing crashes with a SyntaxError?
Can you explain why this line evaluates to true in JavaScript?
console.log([] == ![]); // true
An empty array is equal to the negation of itself?
It looks like a broken language feature, but the JavaScript engine follows strict internal conversion rules.
Here is what happens under the hood during Implicit Type Coercion:
1. Strict Equality (===) vs Loose Equality (==)
• Strict (===): Compares Type + Value. If types differ, it returns false immediately. No conversions occur.
• Loose (==): Compares Value only. If types differ, the engine forces "Implicit Type Coercion" to convert both sides into a common type before comparing.
2. Decoding the Anatomy of [] == ![]
Step 1: Operator Precedence
The Logical NOT (!) operator executes first.
In JavaScript, an array is an object, and all objects are truthy.
Negating a truthy value turns it into boolean false:
![] ➔ false
Now the check becomes:
[] == false
Step 2: The Boolean Coercion Rule
When comparing anything to a Boolean with ==, the engine converts the Boolean to a Number (false becomes 0):
[] == 0
Step 3: The Object-to-Primitive Rule
When comparing an Object/Array to a Number, the engine calls .toString() on the object:
[].toString() produces "" (an empty string).
Now the check becomes:
"" == 0
Step 4: The String-to-Number Rule
When comparing a String to a Number, the engine converts the String to a Number:
Number("") evaluates to 0.
Step 5: The Final Comparison
0 == 0 ➔ true!
3. The Special Case: null and undefined
null == undefined evaluates to true, but:
null == 0 evaluates to false.
Why? The specification explicitly dictates that null and undefined loosely equal each other and nothing else. They do not coerce to numbers under ==.
4. The Production Standard:
This is why modern teams and ESLint enforce the strict equality rule (eqeqeq).
Loose equality (==) introduces silent conversions that hide runtime logic bugs. Always default to strict equality (=== and !==) to keep comparisons explicit and predictable.
Did you know why [] == ![] returned true before seeing the step-by-step trace?
You're at the end of the preview
Log in to keep reading the feed, follow alumni, and join the conversation.