Index

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? this is the fresh object being constructed. This beats everything, including bind - a bound function used as a constructor ignores its bound this.
2. Explicit binding
Called via call, apply, or created with bind? this is whatever you passed. bind is permanent: a bound function cannot be re-bound, and call on it is ignored.
3. Implicit binding
Called as obj.method()? this is obj. 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? this is undefined in strict mode (so: in any module, any class body), and the global object in sloppy mode. Most "cannot read properties of undefined" errors around this are this rule firing.
Outside the list: arrow functions
An arrow has no this of its own at all, so none of the four rules apply to it. this inside an arrow is just a variable looked up the scope chain, resolved where the arrow was WRITTEN. call, apply, bind and new cannot change it.

Questions

Answer each one yourself before you open it.

  1. 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 var loop puzzle prints the same number three times.

  2. 02Give me an example of a closure.

    A counter: an outer function holds let count = 0 and 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).

  3. 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.

  4. 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.

  5. 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.

  6. 06Why does this print 3, 3, 3?

    Because var i is 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 and i is 3.

    The timing is the second half of the answer: setTimeout(fn, 0) doesn't run fn now, it queues it. The synchronous loop completes first, so every callback reads i after the loop has already pushed it to 3.

    Follow-up Two ways to fix it?

    Change var to let, which gives each iteration its own binding. Or wrap the body in an IIFE that takes i as 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.

  7. 07Why does let fix the loop when var doesn't?

    The spec gives a let loop 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 let declared 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.

  8. 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.

  9. 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 - undefined in strict mode, the global object in sloppy mode.

    Say "in that order" and give the order. this is a parameter the engine passes in at each call, which is why a single function can have a different this on every line that calls it.

  10. 10What is this in a plain function call?

    undefined in strict mode, the global object in sloppy mode. Every ES module and every class body is strict, so in modern code the answer is undefined.

    This matters because the difference changes the ERROR you get. Sloppy mode gives you a quiet undefined property 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.

  11. 11Why does this become undefined when I pass a method as a callback?

    Because implicit binding comes from the dot at the CALL site. obj.method is 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 with obj as this". 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.

  12. 12What is this inside a setTimeout callback?

    Not the surrounding object. A plain function passed to setTimeout is called by the timer with no receiver, so it's the global object in a browser (or the Timeout object in Node), and undefined in strict mode.

    Follow-up Fix?

    Pass an arrow function. An arrow has no this of its own, so it uses the enclosing method's - which is what you meant. Before arrows, people wrote const self = this above the call, and you'll still find that in old code.

  13. 13What is this in an arrow function?

    There isn't one. An arrow doesn't get its own this binding, so this inside it resolves up the scope chain like any other variable, to whatever this was 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, apply and bind silently do nothing to an arrow's this, and new on an arrow is a TypeError. That's not a limitation being worked around - it's the whole feature.

  14. 14What bug does an arrow function fix?

    The nested-callback one: a method that calls setTimeout, map, or a promise .then and loses this inside the callback. An arrow inherits the method's this, so it just works and you delete the const self = this line.

  15. 15What bug does an arrow function cause?

    Using one as an object method. { count: 0, inc: () => this.count++ } takes this from 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, this at the top level is undefined, so you get a TypeError; in a browser script it's window, so you get NaN and no error at all.

    Follow-up Where else must you not use one?

    As a constructor (new throws), as a prototype method, as a DOM event handler where you want this to be the element, and anywhere you need arguments - arrows don't have that either. Also any function that must be bind-able by a caller.

  16. 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, so this inside was undefined. Binding in the constructor produced one permanently-bound copy per instance.

    Doing it in the constructor rather than in render was 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 any memo/PureComponent optimisation stops working. The constructor version is created once.

    Follow-up What replaced it?

    Class fields with an arrow - handleClick = () => {} - which captures this lexically at instance creation and gives the same stable identity. And then hooks, which removed this from React components entirely; that's really why nobody writes this any more.

  17. 17call vs apply vs bind?

    call and apply both invoke immediately with a chosen this; call takes the arguments listed, apply takes them as an array. bind doesn't invoke - it returns a new function with this (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. apply mattered a lot before spread; today fn.call(obj, ...args) covers most of what apply was for.

    Follow-up Can you rebind a bound function?

    No. bind is permanent - binding a bound function again keeps the first this, and call/apply on it are ignored. The only thing that overrides it is new.

  18. 18What does new actually do?

    Four steps: create an empty object whose prototype is the function's .prototype, call the function with this set 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.

  19. 19Which wins, new or bind?

    new. It's first in the precedence list - a bound function called with new ignores its bound this and constructs normally. Bound leading arguments do still apply.

  20. 20What is this at 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 NaN in another. Same code, different top-level this.

  21. 21Does this in a DOM event handler point at the element?

    In a regular function handler, yes - addEventListener calls it with the element as the receiver. In an arrow function handler, no: it takes this from the surrounding scope, and you use event.currentTarget instead.

    event.currentTarget is the safe answer in both cases and is what you should reach for anyway. event.target is a different thing - the element actually clicked, which may be a descendant.

  22. 22What is currying?

    Turning a function of N arguments into N functions of one argument each, so add(1, 2, 3) becomes add(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.

  23. 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.

  24. 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 towards length. Use it where it reads better, not everywhere.

  25. 25How would you write a generic curry?

    Collect arguments; if you have at least fn.length of them, call the function; otherwise return a function that collects more and recurses. Six lines, and the whole trick is fn.length telling you the arity.

    Follow-up What breaks it?

    fn.length counts only parameters before the first default or rest parameter, so (a, b = 1, c) reports 1 and (...args) reports 0. A curried function whose arity reports 0 calls immediately and never curries.

  26. 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 this and arguments - fn.apply(this, args) rather than fn(...args) - so it still works on a method.

  27. 27Write memoize.

    A closure over a Map keyed 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, undefined values, 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.

  28. 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.

  29. 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.

  30. 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.

  31. 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.

  32. 32Why does this inside a class method break when passed to addEventListener?

    Same reason as any method: the dot is lost at the call site. Class bodies are strict mode, so this becomes undefined and 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.

  33. 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.

  34. 34Does an arrow function have arguments?

    No. arguments inside an arrow resolves to the enclosing function's, the same way this does - 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
3

var 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
2

A 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
2

The 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 1

Each 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
2

Both 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
undefined

timer.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
undefined

An 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
2

Here 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
first

bind 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
1

new 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
2

The 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
16

Each 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 called flag rather than checking result - otherwise a function returning undefined runs every time.
  • fn.apply(this, args) rather than fn(...args) so it still works when used as a method.
  • Dropping fn afterwards 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);         // 1
  • cache.has(key) not cache.get(key) - otherwise a cached undefined or 0 recomputes every time.
  • JSON.stringify as 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 WeakMap keyed 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 = this on the first line: inside myBind, this IS the function being bound, because it was called as greet.myBind(...).
  • The isNew check is the part interviewers are waiting for - new must beat the bound this, which is rule 1 beating rule 2.
  • Leading arguments are stored and prepended, which is why bind is 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));   // 24
  • fn.length is the declared arity and is the only thing telling curried when 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 call
  • timer living between calls is the closure - that's the whole answer to "where is the closure here?".
  • The inner setTimeout callback is an arrow so this and args come 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.
← Scope, hoisting and the TDZTypes, coercion and equality →