Understanding this and Closures in JavaScript
Two JavaScript concepts separate junior developers from advanced engineers:
- Closures
this
Both concepts appear related to function context, but internally they work in completely different ways.
- Closures depend on where functions are created.
thisdepends on how functions are called.
What Are Closures?
Closures allow functions to remember variables from their outer lexical scope even after the outer function finishes execution.
function outer() {
const message =
'Hello';
function inner() {
console.log(message);
}
return inner;
}
const fn = outer();
fn();
Even though outer() already finished executing, the inner function still remembers the variable.
Lexical Scope
JavaScript uses lexical scoping.
This means functions can access variables based on where they were written in the source code.
function parent() {
const value = 10;
function child() {
console.log(value);
}
child();
}
The child function can access variables from its parent scope automatically.
The Famous setTimeout Loop Problem
One of the most common JavaScript interview questions involves closures and loops.
Problem with var
for (var i = 1; i <= 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
The output becomes:
4
4
4
Because var is function-scoped, all callbacks reference the same variable.
Solution with let
for (let i = 1; i <= 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
Now the output becomes:
1
2
3
Each loop iteration creates a new block-scoped variable.
Understanding this
Unlike closures, this is determined dynamically at runtime.
Do not ask where the function was written. Ask how the function was called.
Default Binding
function test() {
console.log(this);
}
test();
In strict mode, this becomes undefined.
Implicit Binding
const user = {
name: 'Alice',
greet() {
console.log(this.name);
}
};
user.greet();
Because the function is called through user, this refers to that object.
Explicit Binding
JavaScript allows developers to manually control this.
function greet() {
console.log(this.name);
}
const user = {
name: 'Alice'
};
greet.call(user);
The call() method explicitly defines the value of this.
The new Keyword
function User(name) {
this.name = name;
}
const user =
new User('Alice');
The new keyword creates a new object and binds this automatically.
The Classic Lost this Problem
const user = {
name: 'Alice',
greet() {
setTimeout(function() {
console.log(this.name);
}, 1000);
}
};
user.greet();
The callback loses the original object context.
Inside the callback, this no longer refers to user.
Arrow Functions Solve This
Arrow Functions do not create their own this.
Instead, they inherit this lexically from the surrounding scope.
const user = {
name: 'Alice',
greet() {
setTimeout(() => {
console.log(this.name);
}, 1000);
}
};
user.greet();
Now the callback correctly accesses the original object.
Why Arrow Functions Matter
Arrow Functions solved one of the biggest historical problems in JavaScript callback handling.
This is why they became extremely common in React and Node.js applications.
Closures vs this
Closures
- Based on lexical scope
- Determined during creation
- Remember variables
- Predictable behavior
this
- Based on invocation
- Determined during execution
- Changes dynamically
- Depends on call-site
Best Practices
- Prefer
letandconst - Use Arrow Functions for callbacks
- Understand lexical scope deeply
- Avoid relying on implicit
this - Learn closure behavior with async code
Conclusion
Closures and this are two of the most important foundations of modern JavaScript.
Closures explain how functions remember variables.
this explains how functions receive execution context.
Mastering both concepts dramatically improves understanding of asynchronous JavaScript, React, Node.js, and modern frontend architecture.