[Hard] π Closure
1. What is Closure ?β
What is a closure?
To understand closures, you should first understand JavaScript variable scope and how a function accesses outer variables.
Variable Scopeβ
In JavaScript, variable scope is commonly discussed as global scope and function scope (and block scope with let/const).
// global scope
let a = 1;
function parentFunction() {
// function scope
let b = 2;
function childFunction() {
let c = 3;
console.log(a, b, c); // print 1 2 3, can access global + outer function scope
}
childFunction();
}
parentFunction();
console.log(a); // print 1, can access global scope
console.log(b, c); // error: cannot access variables inside function scope
Closure Exampleβ
A closure is formed when a child function is defined inside a parent function and returned, so the child function keeps access to the parent's lexical environment (which avoids immediate garbage collection for captured variables).
function parentFunction() {
let count = 0;
return function childFunction() {
count += 1;
console.log(`Current count: ${count}`);
};
}
const counter = parentFunction();
counter(); // print Current count: 1
counter(); // print Current count: 2
// `count` is preserved because childFunction still exists and keeps a reference
Be careful: closures keep variables in memory. Overuse can increase memory usage and hurt performance.
2. Create a function that meets the following conditionsβ
Create a function (using closure concepts) that satisfies:
plus(2, 5); // output 7
plus(2)(5); // output 7
First Solution: two functionsβ
Split into two function styles:
function plus(value, subValue) {
return value + subValue;
}
console.log(plus(2, 5));
// use closure to save value
function plus(value) {
return function (subValue) {
return value + subValue;
};
}
console.log(plus(2)(5));
Second Solution: single functionβ
The first approach may be rejected in interviews if they ask for one function that handles both styles.
function plus(value, subValue) {
// determine behavior by number of arguments
if (arguments.length > 1) {
return value + subValue;
} else {
return function (item) {
return value + item;
};
}
}
console.log(plus(2, 5));
console.log(plus(2)(5));
3. Please take advantage of the closure feature to increase the numberβ
Use closures to implement incremental counting:
function plus() {
// code
}
var obj = plus();
obj.add(); // print 1
obj.add(); // print 2
First Solution: return variable containerβ
Use a normal function style here (no arrow function required).
function plus() {
let cash = 0;
let counter = {
add() {
cash += 1;
console.log(cash);
},
};
return counter;
}
var obj = plus();
obj.add();
obj.add();
Second Solution: return object directlyβ
You can also wrap the object directly in return.
function plus() {
let cash = 0;
return {
add: function () {
cash += 1;
console.log(cash);
},
};
}
var obj = plus();
obj.add();
obj.add();
4. What will be printed in this nested function call?β
What is the output of this nested function call?
function a(aa) {
aa();
}
function b(bb) {
bb();
}
function c() {
console.log('hello');
}
a(b(c));
Analysisβ
Output:
hello
TypeError: aa is not a function
Detailed Execution Flowβ
// Execute a(b(c))
// JavaScript evaluates function calls from inner to outer
// Step 1: evaluate inner b(c)
b(c)
β
// c is passed into b
// inside b, bb() means c()
c() // prints 'hello'
β
// b has no return statement
// so it returns undefined
return undefined
// Step 2: evaluate a(undefined)
a(undefined)
β
// undefined is passed into a
// a tries aa(), i.e. undefined()
undefined() // β TypeError: aa is not a function
Why?β
1. Function evaluation order (inner -> outer)β
// Example
console.log(add(multiply(2, 3)));
β β
| ββ 2. execute multiply(2, 3) first -> 6
βββββββ 3. then execute add(6)
// Same idea
a(b(c))
β β
| ββ 1. evaluate b(c)
ββββ 2. then evaluate a(result of b(c))
2. A function without return returns undefinedβ
function b(bb) {
bb(); // executes, but no return
} // implicit return undefined
// Equivalent to
function b(bb) {
bb();
return undefined; // added implicitly by JavaScript
}
3. Calling a non-function throws TypeErrorβ
const notAFunction = undefined;
notAFunction(); // TypeError: notAFunction is not a function
// other error cases
null(); // TypeError
123(); // TypeError
'string'(); // TypeError
How to fix it?β
Method 1: make b return a functionβ
function a(aa) {
aa();
}
function b(bb) {
bb();
return function () {
console.log('b executed');
};
}
function c() {
console.log('hello');
}
a(b(c));
// output:
// hello
// b executed
Method 2: pass a function reference, do not execute too earlyβ
function a(aa) {
aa();
}
function b(bb) {
return function () {
bb();
};
}
function c() {
console.log('hello');
}
a(b(c)); // prints 'hello'
// or
a(() => b(c)); // prints 'hello'
Method 3: change execution flowβ
function a(aa) {
aa();
}
function b(bb) {
bb();
}
function c() {
console.log('hello');
}
// execute separately
b(c); // prints 'hello'
a(() => console.log('a executed')); // prints 'a executed'
Related Interview Variationsβ
Question 1: what if we change it like this?β
function a(aa) {
return aa();
}
function b(bb) {
return bb();
}
function c() {
console.log('hello');
return 'world';
}
console.log(a(b(c)));
Click to view answer
hello
TypeError: aa is not a function
Explanation:
b(c)-> runsc(), prints'hello', returns'world'a('world')-> tries to execute'world'()'world'is a string, not a function, so it throws TypeError
Question 2: what if all functions return values?β
function a(aa) {
return aa;
}
function b(bb) {
return bb;
}
function c() {
return 'hello';
}
const result = a(b(c));
console.log(result);
console.log(result());
Click to view answer
[Function: c]
hello
Explanation:
b(c)-> returns functioncitself (not executed)a(c)-> returns functioncresultis functioncresult()-> executesc(), returns'hello'
Key Takeawaysβ
// function call precedence
a(b(c))
β
// 1. evaluate inner call first
b(c) // if b has no return, result is undefined
β
// 2. then evaluate outer call
a(undefined) // calling undefined() throws
// fixes
// β
1. ensure the middle function returns a function
// β
2. or wrap with an arrow function
a(() => b(c))