Closures, this, and currying
One sentence carries this entire topic: a function's variables are decided by where it was written, and its this is decided by how it was called. Closures are the first half - the scope outliving the call that made it. Every this bug is the second half - a function detached from the object it was written next to. Arrow functions are what you get when someone applies the first rule to the second problem. Say that sentence early and most follow-ups answer themselves.
A closure is a function plus the scope it was born in
When a function is created, it keeps a reference to the environment record of the scope that contained it. Normally that record is discarded when the outer call returns. If an inner function survives - returned, stored, passed to setTimeout, registered as a listener - the record cannot be discarded, because the inner function still points at it. That surviving pair is the closure.
Two things people get wrong. First: it closes over the VARIABLE, not the value. Two closures made in the same scope see each other's writes, and a closure made before a reassignment sees the new value afterwards. Second: every function is a closure, technically. What makes it interesting is only whether the scope outlives the call.
The four `this` rules, in precedence order
Work down this list and stop at the first match. Interviewers ask it as "how is this determined?" and the correct answer is a list, in order, not a paragraph.
- 1. `new` binding
- Called with
new?thisis the fresh object being constructed. This beats everything, includingbind- a bound function used as a constructor ignores its boundthis. - 2. Explicit binding
- Called via
call,apply, or created withbind?thisis whatever you passed.bindis permanent: a bound function cannot be re-bound, andcallon it is ignored. - 3. Implicit binding
- Called as
obj.method()?thisisobj. It is the dot at the call site that does this, and nothing else - which is why the binding is lost the moment the function is pulled off the object and called on its own. - 4. Default binding
- None of the above?
thisisundefinedin strict mode (so: in any module, any class body), and the global object in sloppy mode. Most "cannot read properties of undefined" errors aroundthisare this rule firing. - Outside the list: arrow functions
- An arrow has no
thisof its own at all, so none of the four rules apply to it.thisinside an arrow is just a variable looked up the scope chain, resolved where the arrow was WRITTEN.call,apply,bindandnewcannot change it.
Questions
Answer each one yourself before you open it.
01What is a closure?
A function that still has access to the variables of the scope it was defined in, even after that scope has finished running. The function and that surrounding state travel together.
Mechanically, a function object holds a reference to the environment record of its defining scope. Normally that record is garbage once the call returns; if the inner function escapes, the reference keeps it alive. So a closure isn't a copy of the variables - it's a live link to the same bindings.
Follow-up So does it capture the value or the variable?
The variable. Two closures over the same scope share one binding, so a write through one is visible through the other. That is exactly why the
varloop puzzle prints the same number three times.02Give me an example of a closure.
A counter: an outer function holds
let count = 0and returns an inner function that increments and returns it. Every call to the outer function makes a fresh, independent counter with private state.It's the right example because it demonstrates both halves: the state persists between calls (so the scope outlived the call), and two counters don't interfere (so each closure got its own environment record).
03When is a closure created?
Every time a function is created - so on every call to an outer function, not once at definition. That's why two calls to a counter factory give two independent counters.
04What are closures actually used for?
Private state without classes, factory functions, callbacks that need to remember something,
once, memoisation, debounce and throttle, partial application, and module-scoped configuration. Effectively: any time a function needs to remember something between calls without a global.Name three concrete ones rather than saying "data privacy" and stopping. "Debounce is a closure over a timer id" and "memoise is a closure over a cache" are the answers that show you've written them, and they're both four lines you can produce on demand.
05How do you get a private variable in JavaScript?
Close over it. A variable in a function scope with no reference escaping is unreachable from outside - nothing is marked private, it's simply not addressable.
Follow-up And in a class?
#field- real private fields, enforced by the engine, a SyntaxError to access from outside. Before that, people used a leading underscore, which is a naming convention and no protection at all, or a WeakMap keyed by instance.06Why does this print 3, 3, 3?
Because
var iis one binding shared by the whole function, and all three callbacks close over that same binding. By the time the timers run, the loop has finished andiis3.The timing is the second half of the answer:
setTimeout(fn, 0)doesn't runfnnow, it queues it. The synchronous loop completes first, so every callback readsiafter the loop has already pushed it to3.Follow-up Two ways to fix it?
Change
vartolet, which gives each iteration its own binding. Or wrap the body in an IIFE that takesias a parameter, which copies the current value into a new scope - that was the pre-2015 fix.setTimeout(fn, 0, i)also works, since extra arguments are passed to the callback.07Why does
letfix the loop whenvardoesn't?The spec gives a
letloop variable a fresh binding on every iteration and copies the previous value into it. So each callback closes over a different variable, holding the value from its own iteration.This is a genuine special case in the spec, not a consequence of block scoping - a
letdeclared inside the body would also be per-iteration, but here it's the loop HEAD that gets copied, which is a rule written specifically to make this pattern work. Knowing that it's deliberate is the follow-up answer.08Do closures cause memory leaks?
Not by themselves. A closure keeps its scope alive for as long as the closure is reachable - which is correct behaviour. It becomes a leak when something long-lived holds a closure that captured something large it doesn't need.
The usual real case is an event listener on a long-lived element that closes over a big object or a whole component, never removed. The fix is removing the listener, not avoiding closures. The second case is a closure over a huge array where you only needed its length - capture the length, not the array.
09What determines the value of
this?How the function was called, not where it was defined. Four rules in order:
new, then explicit binding with call/apply/bind, then implicit binding via a dot at the call site, then the default -undefinedin strict mode, the global object in sloppy mode.Say "in that order" and give the order.
thisis a parameter the engine passes in at each call, which is why a single function can have a differentthison every line that calls it.10What is
thisin a plain function call?undefinedin strict mode, the global object in sloppy mode. Every ES module and every class body is strict, so in modern code the answer isundefined.This matters because the difference changes the ERROR you get. Sloppy mode gives you a quiet
undefinedproperty read that fails later; strict mode gives you "Cannot read properties of undefined" right at the access. The second is much easier to debug, which is why the change was made.11Why does
thisbecome undefined when I pass a method as a callback?Because implicit binding comes from the dot at the CALL site.
obj.methodis just a reference to the function; passing it strips the object, and whoever calls it later calls it plainly, so the default rule applies.The mental model that fixes this for good: a method isn't attached to its object. It's a value stored in a property, and
obj.method()is really "get the function, then call it withobjasthis". Pull the value out and you kept the function and dropped the second half.Follow-up The two fixes?
obj.method.bind(obj), which returns a new function permanently bound; or wrap it in an arrow -() => obj.method()- which preserves the dot at the actual call. The arrow version is what most people write now because it also lets you pass arguments through.12What is
thisinside asetTimeoutcallback?Not the surrounding object. A plain function passed to
setTimeoutis called by the timer with no receiver, so it's the global object in a browser (or the Timeout object in Node), andundefinedin strict mode.Follow-up Fix?
Pass an arrow function. An arrow has no
thisof its own, so it uses the enclosing method's - which is what you meant. Before arrows, people wroteconst self = thisabove the call, and you'll still find that in old code.13What is
thisin an arrow function?There isn't one. An arrow doesn't get its own
thisbinding, sothisinside it resolves up the scope chain like any other variable, to whateverthiswas where the arrow was written.Because it's a normal scope lookup, it's fixed at write time and cannot be changed at all.
call,applyandbindsilently do nothing to an arrow'sthis, andnewon an arrow is a TypeError. That's not a limitation being worked around - it's the whole feature.14What bug does an arrow function fix?
The nested-callback one: a method that calls
setTimeout,map, or a promise.thenand losesthisinside the callback. An arrow inherits the method'sthis, so it just works and you delete theconst self = thisline.15What bug does an arrow function cause?
Using one as an object method.
{ count: 0, inc: () => this.count++ }takesthisfrom the surrounding module or script scope, not the object, so it silently reads the wrong thing. Same problem on a prototype method.It fails quietly rather than loudly, which makes it worse. In a module,
thisat the top level isundefined, so you get a TypeError; in a browser script it'swindow, so you getNaNand no error at all.Follow-up Where else must you not use one?
As a constructor (
newthrows), as a prototype method, as a DOM event handler where you wantthisto be the element, and anywhere you needarguments- arrows don't have that either. Also any function that must bebind-able by a caller.16Why did old React class components write
this.handleClick = this.handleClick.bind(this)in the constructor?Because
onClick={this.handleClick}passes the function to React, which calls it plainly - implicit binding is lost, sothisinside was undefined. Binding in the constructor produced one permanently-bound copy per instance.Doing it in the constructor rather than in
renderwas deliberate:this.handleClick.bind(this)written in JSX creates a NEW function on every render, so the child gets a different prop identity each time and anymemo/PureComponentoptimisation stops working. The constructor version is created once.Follow-up What replaced it?
Class fields with an arrow -
handleClick = () => {}- which capturesthislexically at instance creation and gives the same stable identity. And then hooks, which removedthisfrom React components entirely; that's really why nobody writes this any more.17
callvsapplyvsbind?callandapplyboth invoke immediately with a chosenthis;calltakes the arguments listed,applytakes them as an array.binddoesn't invoke - it returns a new function withthis(and optionally some leading arguments) locked in.The one-liner people use to remember it: A for array, C for comma, B for bind-it-for-later.
applymattered a lot before spread; todayfn.call(obj, ...args)covers most of whatapplywas for.Follow-up Can you rebind a bound function?
No.
bindis permanent - binding a bound function again keeps the firstthis, andcall/applyon it are ignored. The only thing that overrides it isnew.18What does
newactually do?Four steps: create an empty object whose prototype is the function's
.prototype, call the function withthisset to that object, and return that object - unless the function explicitly returns an object of its own, in which case that wins.The "unless it returns an object" clause is the follow-up. Returning a primitive is ignored and you still get the constructed object; returning an object replaces it entirely, which is how factory-in-constructor tricks work.
19Which wins,
neworbind?new. It's first in the precedence list - a bound function called withnewignores its boundthisand constructs normally. Bound leading arguments do still apply.20What is
thisat the top level?In an ES module,
undefined. In a classic script, the global object. In a Node CommonJS file,module.exports, which is an empty object.Worth knowing because it explains why the arrow-as-object-method bug produces a TypeError in one file and a silent
NaNin another. Same code, different top-levelthis.21Does
thisin a DOM event handler point at the element?In a regular function handler, yes -
addEventListenercalls it with the element as the receiver. In an arrow function handler, no: it takesthisfrom the surrounding scope, and you useevent.currentTargetinstead.event.currentTargetis the safe answer in both cases and is what you should reach for anyway.event.targetis a different thing - the element actually clicked, which may be a descendant.22What is currying?
Turning a function of N arguments into N functions of one argument each, so
add(1, 2, 3)becomesadd(1)(2)(3). Each call returns a new function closing over what it's been given so far.The mechanism is entirely closures - that's why it lives on this page. Each returned function keeps a reference to the scope containing the earlier arguments.
23Currying vs partial application?
Currying is strict: one argument at a time, all the way down, always producing N unary functions. Partial application just fixes SOME arguments and returns a function taking the rest -
fn.bind(null, 1, 2)is partial application, in one call.Most real code wants partial application and calls it currying. The interview answer is the distinction plus the honest note that libraries like Lodash blur it deliberately:
_.curry(fn)(1, 2)(3)accepts either style.24What is currying actually for?
Making specialised functions out of general ones without a wrapper -
const double = multiply(2)- and building pipelines where every step takes exactly one value. It's also how you get configuration out of a hot path: bind the config once, call the result many times.Follow-up Downside?
Every call allocates a function, stack traces get deeper and less readable, and
fn.length-based generic currying breaks on rest parameters and default parameters, which don't count towardslength. Use it where it reads better, not everywhere.25How would you write a generic
curry?Collect arguments; if you have at least
fn.lengthof them, call the function; otherwise return a function that collects more and recurses. Six lines, and the whole trick isfn.lengthtelling you the arity.Follow-up What breaks it?
fn.lengthcounts only parameters before the first default or rest parameter, so(a, b = 1, c)reports1and(...args)reports0. A curried function whose arity reports0calls immediately and never curries.26Write
once.A closure over a boolean and a stored result: call through the first time, cache the result, return the cache forever after.
It's the smallest useful closure and a common warm-up. The detail that gets noticed is preserving
thisand arguments -fn.apply(this, args)rather thanfn(...args)- so it still works on a method.27Write
memoize.A closure over a
Mapkeyed by the arguments. If the key is present, return the cached value; otherwise compute, store, return.Follow-up What's the problem with the key?
JSON.stringify(args)is the usual key and it's wrong for anything with functions,undefinedvalues,Maps, or key order that varies. It's also unbounded - a real memoise needs an eviction policy, or it's a leak with a friendly name.28Write
debounce, and how is it a closure?It closes over a timer id. Each call clears the pending timer and sets a new one, so the wrapped function only runs once the calls stop for the given delay. The timer id living between calls is the closure.
Follow-up Difference from throttle?
Debounce waits for quiet and runs once at the end - right for a search-as-you-type box. Throttle runs at most once per interval regardless of how many calls arrive - right for scroll and resize handlers.
29Do two functions returned from the same call share a closure?
Yes. They close over the same environment record, so writes through one are visible through the other. That's how the module pattern gives you several methods over one piece of private state.
30Can you access a closed-over variable from outside?
Not from code. There's no reflection API for it - the only way in is through a function that was defined in that scope. That's what makes it real privacy rather than a convention.
31What's the difference between a closure and a class instance?
Almost none in capability - both bundle state with behaviour. Closures give genuinely private state and cheap one-off objects; classes give a shared prototype, so a thousand instances share one copy of each method instead of allocating one per instance.
The practical answer: for a handful of objects it doesn't matter, pick whichever reads better. For very large numbers of objects, the prototype version uses noticeably less memory because the methods aren't duplicated.
32Why does
thisinside a class method break when passed toaddEventListener?Same reason as any method: the dot is lost at the call site. Class bodies are strict mode, so
thisbecomesundefinedand you get a TypeError rather than a silent wrong answer.Follow-up Fix?
Bind in the constructor, define the method as an arrow class field, or pass
() => this.handle(e). The class-field arrow is the common one, at the cost of one function per instance instead of one on the prototype.33When would you NOT use a closure?
When the state doesn't need to be private and the function is called on a hot path with many instances - each closure allocates a new environment and new function objects. And when it makes a value invisible to the debugger that someone will need to inspect.
34Does an arrow function have
arguments?No.
argumentsinside an arrow resolves to the enclosing function's, the same waythisdoes - and at the top level there isn't one, so it throws. Use rest parameters instead.
Predict the output
The output is hidden. Predict it before you open it.
The one everybody is asked
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 0);
}Show the output
3
3
3var i is one binding for the whole enclosing function, and all three callbacks close over that same binding. The loop is synchronous and finishes before any timer fires, leaving i at 3 - the value that failed the condition.
The same loop, one keyword changed
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 0);
}Show the output
0
1
2A let loop head gets a fresh binding per iteration, with the previous value copied in. Each callback closes over a different variable, so each keeps its own iteration's value. This is a rule written into the spec specifically for this pattern.
The pre-2015 fix
for (var i = 0; i < 3; i++) {
(function (n) {
setTimeout(function () {
console.log(n);
}, 0);
})(i);
}Show the output
0
1
2The IIFE creates a new function scope per iteration and copies the current i into the parameter n. Each callback now closes over a different n. setTimeout(fn, 0, i) does the same thing more cheaply, since extra arguments are forwarded to the callback.
Two counters, one factory
function counter() {
let n = 0;
return () => ++n;
}
const a = counter();
const b = counter();
console.log(a(), a(), b());Show the output
1 2 1Each call to counter runs the body fresh and creates a new n, so a and b close over different bindings. Arguments to console.log evaluate left to right, so a() runs twice before b() - hence 1, 2, then 1.
Two closures, one variable
function pair() {
let n = 0;
return [() => ++n, () => n];
}
const [inc, read] = pair();
inc();
inc();
console.log(read());Show the output
2Both arrows were created in the same call, so they share one environment record and one n. A closure captures the variable, not a snapshot of its value - this is the same fact the var loop puzzle is testing, seen from the other side.
The dot at the call site
const timer = {
label: 'tick',
show() {
console.log(this.label);
},
};
timer.show();
const f = timer.show;
f();Show the output
tick
undefinedtimer.show() has the dot, so implicit binding applies. f() doesn't, so the default rule does: in this non-strict script this is the global object, which has no label. In an ES module or any strict context the second call throws TypeError: Cannot read properties of undefined (reading 'label') instead - same cause, louder failure.
An arrow as an object method
const box = {
count: 10,
get: () => console.log(this.count),
};
box.get();Show the output
undefinedAn arrow has no this of its own, so it takes the one from where it was WRITTEN - the surrounding script scope, not the object literal. Object literals don't create a this scope. In a browser script that's window, which has no count; in an ES module this is undefined and this line throws instead.
An arrow where it belongs
const box = {
items: ['a', 'b'],
show() {
setTimeout(() => console.log(this.items.length), 0);
},
};
box.show();Show the output
2Here the arrow is written inside a regular method, so its this resolves to the method's - which implicit binding set to box. This is exactly the bug arrows were added to fix, and the const self = this line it replaced.
The same thing with a regular function
const box = {
items: ['a', 'b'],
show() {
setTimeout(function () {
console.log(this.items.length);
}, 0);
},
};
box.show();Show the output
TypeError: Cannot read properties of undefined (reading 'length')The timer calls the callback with no receiver, so this is not box. this.items is undefined, and reading .length off it throws. Note the property named in the error is length, not items - the first lookup succeeded and returned undefined.
call, apply, bind
function greet(greeting, mark) {
console.log(greeting + ', ' + this.name + mark);
}
const p = { name: 'Nova' };
greet.call(p, 'Hi', '!');
greet.apply(p, ['Hi', '!']);
const bound = greet.bind(p);
bound('Hi', '!');Show the output
Hi, Nova!
Hi, Nova!
Hi, Nova!All three set this to p. call takes arguments listed, apply takes them as an array, and bind returns a new function you call later. The only difference visible here is when the call happens.
Binding a bound function
function who() {
console.log(this.tag);
}
const first = who.bind({ tag: 'first' });
const second = first.bind({ tag: 'second' });
second();
first.call({ tag: 'third' });Show the output
first
firstbind is permanent. The second bind wraps a function whose this is already fixed, and call on a bound function is ignored for the same reason. The only thing that overrides a bound this is new.
new beats bind
function Thing() {
this.v = 1;
}
const Bound = Thing.bind({ v: 99 });
const made = new Bound();
console.log(made.v);Show the output
1new is first in the precedence order, so it discards the bound this and supplies the freshly created object instead. Bound leading ARGUMENTS still apply under new - only the receiver is overridden.
Implicit binding follows the object, not the definition
const one = {
v: 1,
get() {
return this.v;
},
};
const two = { v: 2, get: one.get };
console.log(one.get());
console.log(two.get());Show the output
1
2The same function object is reachable through both. this comes from whatever is to the left of the dot at the moment of the call, so the identical function returns two different values. Nothing about where it was defined enters into it.
Currying by hand
const add = (a) => (b) => (c) => a + b + c;
console.log(add(1)(2)(3));
const addTen = add(10);
console.log(addTen(5)(1));Show the output
6
16Each arrow returns another arrow that closes over the argument it was given. addTen is the middle function, holding a = 10, reusable - that reuse is the practical point of currying rather than the syntax.
Write it from memory
Complete, runnable code. Practice typing it from memory before your interview.
once
Run the wrapped function at most once, and return the first result forever after.
function once(fn) {
let called = false;
let result;
return function (...args) {
if (!called) {
called = true;
result = fn.apply(this, args);
fn = null; // let the original be collected
}
return result;
};
}
const init = once(() => {
console.log('initialising');
return { ready: true };
});
console.log(init()); // logs 'initialising', then { ready: true }
console.log(init()); // { ready: true } - no log- A separate
calledflag rather than checkingresult- otherwise a function returningundefinedruns every time. fn.apply(this, args)rather thanfn(...args)so it still works when used as a method.- Dropping
fnafterwards matters if the original closed over something large.
memoize
Cache results by argument list. The classic follow-up to "write a closure".
function memoize(fn, keyFn = (...args) => JSON.stringify(args)) {
const cache = new Map();
return function (...args) {
const key = keyFn(...args);
if (cache.has(key)) return cache.get(key);
const value = fn.apply(this, args);
cache.set(key, value);
return value;
};
}
let calls = 0;
const slowSquare = memoize((n) => {
calls++;
return n * n;
});
console.log(slowSquare(4)); // 16
console.log(slowSquare(4)); // 16
console.log(calls); // 1cache.has(key)notcache.get(key)- otherwise a cachedundefinedor0recomputes every time.JSON.stringifyas a key fails on functions,undefined,Maps, and objects whose key order differs. That's why the key function is a parameter.- It's unbounded. Say that out loud - a real one needs an LRU or a
WeakMapkeyed by object argument, or it's a leak.
myBind
Reimplement Function.prototype.bind. Asked because it forces you to use this, closures and rest arguments in one answer.
Function.prototype.myBind = function (thisArg, ...boundArgs) {
const fn = this;
if (typeof fn !== 'function') {
throw new TypeError('myBind must be called on a function');
}
function bound(...callArgs) {
// Called with new? Ignore thisArg and construct normally.
const isNew = this instanceof bound;
return fn.apply(isNew ? this : thisArg, [...boundArgs, ...callArgs]);
}
bound.prototype = Object.create(fn.prototype || null);
return bound;
};
function greet(greeting, name) {
return greeting + ', ' + name + ' (from ' + this.place + ')';
}
const atShop = greet.myBind({ place: 'the shop' }, 'Hi');
console.log(atShop('Nova')); // Hi, Nova (from the shop)const fn = thison the first line: insidemyBind,thisIS the function being bound, because it was called asgreet.myBind(...).- The
isNewcheck is the part interviewers are waiting for -newmust beat the boundthis, which is rule 1 beating rule 2. - Leading arguments are stored and prepended, which is why
bindis also partial application.
curry
Generic currying driven by fn.length. Accepts arguments one at a time or in groups.
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function (...more) {
return curried.apply(this, [...args, ...more]);
};
};
}
function volume(l, w, h) {
return l * w * h;
}
const v = curry(volume);
console.log(v(2)(3)(4)); // 24
console.log(v(2, 3)(4)); // 24
console.log(v(2)(3, 4)); // 24
console.log(v(2, 3, 4)); // 24fn.lengthis the declared arity and is the only thing tellingcurriedwhen to stop collecting.- It breaks on rest and default parameters:
(...args)reports length 0 and(a, b = 1)reports 1, so the function fires immediately. - Strict currying - exactly one argument per call - is
const curry = fn => a => b => …, hand-written per arity. The generic version above is the useful one.
debounce
The closure question disguised as a practical one. Expect it after "what are closures for?".
function debounce(fn, delay) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
const search = debounce((term) => {
console.log('searching for ' + term);
}, 300);
search('a');
search('ab');
search('abc'); // only this one runs, 300ms after the last calltimerliving between calls is the closure - that's the whole answer to "where is the closure here?".- The inner
setTimeoutcallback is an arrow sothisandargscome from the wrapper's call. - Throttle is the sibling: run at most once per interval, right for scroll and resize. Debounce waits for quiet, right for typing.