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

  1. Concept
  2. Why this matters
  3. The two assistants — an analogy
  4. The isolation example — traced line by line
  5. Rule 1: outward yes, sideways never
  6. Rule 2: fresh every time, gone when popped
  7. Parameters are part of the Variable Environment too
  8. See it live — DevTools
  9. Common misconceptions
  10. Interview questions

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, or temp across 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

Three notepads, one label

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

isolation.js
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.

step 1 / 12
Setup · memory phase
Global EC — memory phase

roomNumber → undefined, both functions fully hoisted. This is the head receptionist's notepad.

isolation.js
1var roomNumber = 1;
2
3function frontDesk() {
4 var roomNumber = 10;
5 console.log(roomNumber);
6}
7
8function housekeeping() {
9 var roomNumber = 100;
10 console.log(roomNumber);
11}
12
13frontDesk();
14housekeeping();
15console.log(roomNumber);
Call stack · one notepad per frame
Global ECrunning
roomNumberundefined
frontDesk{ …code }
housekeeping{ …code }
Console
(nothing printed yet)
Step through the program. Each call gets a fresh notepad with its own roomNumber; each pop throws that notepad away; Global's roomNumber is never touched. · interactive
  1. Global EC — memory phase

    roomNumber → undefined, both functions fully hoisted.

  2. Global EC — code phase begins

    roomNumber = 1 runs.

  3. frontDesk() called

    Brand-new EC pushed, with its own private roomNumber (undefined → 10). Prints 10. Popped — its entire notepad thrown away.

  4. housekeeping() called

    Another brand-new, unrelated EC pushed, with its own private roomNumber (undefined → 100). Prints 100. Popped.

  5. Back in Global

    roomNumber here was never touched by either call — prints 1.

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.

siblings.js
function frontDesk() {
  var roomNumber = 10;
}

function housekeeping() {
  console.log(roomNumber); // ReferenceError — can't see frontDesk's local roomNumber
}

frontDesk();
housekeeping();
step 1 / 4
Running
frontDesk() runs and finishes

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.

siblings.js
1function frontDesk() {
2 var roomNumber = 10;
3}
4
5function housekeeping() {
6 console.log(roomNumber);
7}
8
9frontDesk();
10housekeeping();
(console)
frontDeskpopped · gone
  • roomNumber10
  • outer →Global
housekeeping
not called yet
↓↓ outer
no line ever connects the two boxes above
Global
  • frontDesk{ …code }
  • housekeeping{ …code }
  • outer →null
Step through the lookup. It goes own memory → outer reference → Global → null. There is no arrow to a sibling, so frontDesk's roomNumber is unreachable — and already gone. · interactive

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.

frontDesk()
  • roomNumber10
  • outer →Global
housekeeping()
  • roomNumber100
  • outer →Global
↘↙
no line ever connects these two boxes directly
Global
  • roomNumber1
  • frontDeskfn
  • housekeepingfn
  • outer →null
Both functions point up to Global. Neither one points sideways at the other. · outer references

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.

params.js
function checkIn(guestName) {
  console.log(guestName); // "Aditi" — not undefined, even on this very first line
  var stayNights = 3;
}
checkIn("Aditi");
checkIn("Aditi") EC · paused on line 2
  • guestNameparameter"Aditi" ✓ already assigned
  • stayNightsvar in bodyundefined · until line 3
checkIn's Variable Environment at its very first line. The parameter was filled during the memory phase, because the call site already knew the value; the var still waits for its own line. · first line of the body
IdentifierKindValue at the start of the function
guestNameparameter"Aditi" — already assigned
stayNightsvar inside the bodyundefined, until its own line runs

See It Live — DevTools

  1. Set two breakpoints

    One on the console.log line inside frontDesk, one on the same line inside housekeeping.

  2. Run the isolation example

    Hit the first breakpoint — open the Scope panel and confirm roomNumber reads 10, under a Local bucket separate from Global.

  3. Resume to the second breakpoint

    The earlier Local bucket for frontDesk is completely gone from the panel now — a fresh Local bucket for housekeeping shows roomNumber: 100 instead. Two separate notepads, shown live.

  4. Check the Global bucket throughout

    roomNumber: 1 in 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