Lesson 4 · 10 min read
Functions and Variable Environments
Every function call gets its own private notepad of variables — even the same function, called twice, never shares a page. This lesson is about exactly what goes in that notepad, who's allowed to read it, and when it gets thrown away.
On this page
Concept
Lesson 1 showed that every Execution Context has a memory component, also called the Variable Environment. The Variable Environment is the part of a context that stores the variables and functions declared inside it, as name → value pairs. (To be exact, the official language rules, called the ECMAScript specification, keep var and function declarations here, and let and const in the Lexical Environment. In this series, think of the Variable Environment as “the context's own memory.”) This lesson looks at what happens to it on every function call.
The core idea in one line: every function call gets its own, completely separate Variable Environment — even if two different functions, or the same function called twice, use a variable with the exact same name. A local variable is a variable declared inside a function. Only that function's code can see it.
Why This Matters
- It's why you can safely reuse simple variable names like
result,count, ortempacross dozens of functions in the same file without ever worrying about collisions. - It's the foundation for a rule that is often tested in interviews: a function can look outward for variables, but never sideways into an unrelated function.
- It explains exactly when memory gets cleaned up after a function runs — and sets up the one deliberate exception to that rule, which is closures, one of the biggest topics later in this course. A closure is a function that keeps access to the variables of the place where it was created, even after that place has finished running.
The Two Assistants — An Analogy
The head receptionist keeps her own notepad. On it: “Room Number: 1.” She calls over her front-desk assistant for a quick task — he has his own, completely separate notepad, and if he writes “Room Number: 10” on his page, that's entirely his. It has nothing to do with hers.
Later, she calls the housekeeping assistant over for a different task. He, too, has his own separate notepad — “Room Number: 100” on his page, unrelated to either of the other two.
There are three notepads. All of them have a line labelled “Room Number,” and none of them affects the others.
The Isolation Example — Traced Line By Line
var roomNumber = 1;
function frontDesk() {
var roomNumber = 10;
console.log(roomNumber);
}
function housekeeping() {
var roomNumber = 100;
console.log(roomNumber);
}
frontDesk();
housekeeping();
console.log(roomNumber);Output: 10, then 100, then 1 — three completely different roomNumbers, three separate memory spots, no interference.
roomNumber → undefined, both functions fully hoisted. This is the head receptionist's notepad.
1var roomNumber = 1;23function frontDesk() {4var roomNumber = 10;5console.log(roomNumber);6}78function housekeeping() {9var roomNumber = 100;10console.log(roomNumber);11}1213frontDesk();14housekeeping();15console.log(roomNumber);
| roomNumber | undefined |
| frontDesk | { …code } |
| housekeeping | { …code } |
Global EC — memory phase
roomNumber → undefined, both functions fully hoisted.Global EC — code phase begins
roomNumber = 1runs.frontDesk()calledBrand-new EC pushed, with its own private
roomNumber(undefined → 10). Prints10. Popped — its entire notepad thrown away.housekeeping()calledAnother brand-new, unrelated EC pushed, with its own private
roomNumber(undefined → 100). Prints100. Popped.Back in Global
roomNumberhere was never touched by either call — prints1.
Rule 1: Outward Yes, Sideways Never
A function can see variables from the scope it was written inside. A scope is the part of the code where a name can be seen. This works because every context has a Lexical Environment: its own local memory plus an outer reference, which is a link to the Lexical Environment of the scope around it (Lesson 7 explains this fully). A function can never see another function's local variables just because it ran right before or after it.
function frontDesk() {
var roomNumber = 10;
}
function housekeeping() {
console.log(roomNumber); // ReferenceError — can't see frontDesk's local roomNumber
}
frontDesk();
housekeeping();It writes roomNumber = 10 into its own Variable Environment, then returns. That environment is popped and discarded — by the time line 10 runs, it does not exist.
1function frontDesk() {2var roomNumber = 10;3}45function housekeeping() {6console.log(roomNumber);7}89frontDesk();10housekeeping();
- roomNumber10
- outer →Global
not called yet
- frontDesk{ …code }
- housekeeping{ …code }
- outer →null
The front-desk assistant and the housekeeping assistant don't share notepads just because one worked right before the other. They're siblings, not parent and child — only the head receptionist's notepad (global) is something either of them could ever look up to. Never each other's.
- roomNumber10
- outer →Global
- roomNumber100
- outer →Global
- roomNumber1
- frontDeskfn
- housekeepingfn
- outer →null
Rule 2: Fresh Every Time, Gone When Popped
The moment an Execution Context is popped off the call stack (Lesson 2), its entire Variable Environment is discarded. The garbage collector, the part of the engine that automatically frees memory that nothing can reach any more, is then free to reclaim that memory. Calling the same function 5 times in a row creates 5 separate, independent Variable Environments — never shared, never reused.
There is one deliberate exception to “gone means gone.” It is called a closure, and it gets its own lesson (Lesson 10). For now, remember the normal rule: once popped, the memory is thrown away.
Parameters Are Part Of The Variable Environment Too
A parameter is a name written in the function's brackets, like guestName below. The value you pass in when you call the function is the argument. A parameter already has its real value the instant the function starts — during the memory phase itself — because the argument was already known at the call site (the place in the code where the function is called). That's different from a plain var written inside the body, which still starts as undefined.
function checkIn(guestName) {
console.log(guestName); // "Aditi" — not undefined, even on this very first line
var stayNights = 3;
}
checkIn("Aditi");- guestNameparameter"Aditi" ✓ already assigned
- stayNightsvar in bodyundefined · until line 3
| Identifier | Kind | Value at the start of the function |
|---|---|---|
guestName | parameter | "Aditi" — already assigned |
stayNights | var inside the body | undefined, until its own line runs |
See It Live — DevTools
Set two breakpoints
One on the
console.logline insidefrontDesk, one on the same line insidehousekeeping.Run the isolation example
Hit the first breakpoint — open the Scope panel and confirm
roomNumberreads10, under a Local bucket separate from Global.Resume to the second breakpoint
The earlier Local bucket for
frontDeskis completely gone from the panel now — a fresh Local bucket forhousekeepingshowsroomNumber: 100instead. Two separate notepads, shown live.Check the Global bucket throughout
roomNumber: 1in the Global bucket never changes across either breakpoint. This shows that neither function touched it.
Common Misconceptions
“Two functions using the same variable name will conflict or overwrite each other.” They won't — each function call gets its own isolated Variable Environment. Identical names in different functions never collide.
“A function can access a variable from a sibling function if it was called nearby.” Access only goes outward through the Lexical Environment's outer reference, never sideways to another function's local scope, whatever the call order is.
“Function memory is always cleaned up immediately after the function returns, no exceptions.” True almost always. The exception is when a closure keeps a reference to that memory, which prevents it from being thrown away. Lesson 10 covers this.
Interview Questions
If two different functions both declare var x, do they conflict?
No — each function call gets its own separate Variable Environment, so identically named variables in different functions never interfere with each other.
Can a function access another function's local variables just because it was called right after it?
No. Access only goes outward via the Lexical Environment's outer reference (a link to the surrounding scope) to parent scopes — never sideways to an unrelated function's scope.
What happens to a function's Variable Environment after it returns?
Its Execution Context is popped off the call stack and the Variable Environment is discarded. That memory can then be freed by the garbage collector (the part of the engine that frees memory nothing can reach any more), unless a closure is holding a reference to it.
If the same function is called 3 times, are the Variable Environments shared?
No — three separate, independent Variable Environments are created, one per call, sharing nothing with each other.
Is a function parameter undefined at the very first line of the function body?
No — parameters are assigned their real value during the memory creation phase itself, since the value is already known at the call site, unlike a var declared inside the body.
What is the one exception to a function's memory always being thrown away after it returns?
A closure — when an inner function keeps a live reference to its outer function's variables even after that outer function has finished running.
Published by Jainik · Last updated · How these lessons are made
Keep learning
Related series