Scope, Closure, and Lexical Environment in Simple Terms
When writing JavaScript code, you often notice that some variables are "accessible" while others simply throw errors. Why does this happen? What determines where a variable is accessible? The answer lies in these three key concepts: Scope, Closure, and Lexical Environment.
In this article, I’ll explain everything in a very simple way with examples. Understanding this will help you write more precise and secure code without fearing "ReferenceError"s.
What is Scope?
Scope (միջավայր) refers to where a variable is visible or accessible. In other words, JavaScript determines whether you can use a particular variable in a given section of code or not.
There are three main types of scopes:
- Global Scope – The variable is accessible throughout the entire code.
- Function Scope – The variable is accessible only within the function.
- Block Scope – The variable is accessible only inside `{}` (e.g., if, for, let, const).
// Global Scope
let globalVar = "Hello";
function sayHi() {
// Function Scope
let name = "Anna";
console.log(globalVar); // accessible
console.log(name); // accessible
}
console.log(name); // Error: name is not defined
let and const are block-scoped, while var is only function-scoped.
if (true) {
let x = 10;
var y = 20;
}
console.log(x); // Error: x is not defined
console.log(y); // 20
This distinction is important for understanding when a variable "lives" and when it "dies."
What is Lexical Environment?
Lexical Environment is a fancy term that essentially means where your code is written. JavaScript determines variable accessibility based on where you wrote them—**not where you called them from**.
function outer() {
let name = "Armen";
function inner() {
console.log(name);
}
inner();
}
outer(); // will print "Armen"
In this example, the `inner()` function can use the `name` variable because it was written inside it. This is the essence of "lexical environment." If a function is written in a certain environment, it remembers that environment’s variables.
What is a Closure?
A closure is when a function "remembers" its outer environment’s variables—even when that function is called from somewhere else.
function outer() {
let counter = 0;
return function inner() {
counter++;
console.log("Counter:", counter);
};
}
const count = outer();
count(); // Counter: 1
count(); // Counter: 2
This is the magic of closures. Even though `outer()` has already finished executing, the `inner()` function defined inside it **still has access to `counter`**. The closure "closes over" the outer values.
This is why closures are often used to create private variables.
A Useful Closure Example: Private Variables
function createUser(name) {
let password = "secret123";
return {
getName: function () {
return name;
},
checkPassword: function (guess) {
return guess === password;
}
};
}
const user = createUser("Ani");
console.log(user.getName()); // Ani
console.log(user.checkPassword("wrong")); // false
console.log(user.checkPassword("secret123")); // true
Here, the `password` variable is accessible only via closure—it cannot be modified from the outside. This is similar to having a private field in a class.
Final Thoughts
In JavaScript, scope determines where a variable is accessible, lexical environment determines the environment in which a function was created, and closure allows a function to "remember" that environment regardless of where it is called from.
- Write code in a way that makes it clear which scope a variable belongs to.
- Use closures to create encapsulated data.
- Remember: JavaScript looks at where a function was created, not where it was called from.
If you master these concepts, you’ll write code with more confidence, debug more effectively, and build cleaner projects.