Closures, this, and binding in plain terms. No mysticism. Just what the runtime actually does and the patterns that keep you out of trouble.
Closures in one sentence
A closure is a function that still sees variables from the scope where it was created, even after that outer function has finished.
function makeCounter(start = 0) {
let count = start;
return {
increment() {
count += 1;
return count;
},
read() {
return count;
},
};
}
const counter = makeCounter(10);
counter.increment(); // 11
The inner functions ‘close over’ count. That is how module-private state works without classes – and how event handlers remember configuration.
Closures in the wild
Closures power debounce, memoisation, and factory functions. They also cause classic loop bugs if you are not careful with var in old code – use let in loops so each iteration gets its own binding.
function debounce(fn, delayMs) {
let timerId;
return function (...args) {
clearTimeout(timerId);
timerId = setTimeout(() => fn.apply(this, args), delayMs);
};
}
What this is
this is not the function itself, and it is not the place you defined the function. It is set by how the function is called:
- Method call:
obj.method()–thisisobj. - Plain call:
fn()–thisis undefined in strict mode (modules are strict). - Constructor:
new Fn()–thisis the new object. - Explicit binding:
fn.call(x)/apply/bind.
const button = {
label: 'Save',
handleClick() {
console.log(this.label);
},
};
button.handleClick(); // 'Save'
const loose = button.handleClick;
loose(); // undefined (strict) - lost the receiver
Arrow functions and this
Arrow functions do not have their own this. They inherit this from the surrounding lexical scope. That makes them good for callbacks inside methods when you want the outer this – and bad as object methods if you need the object as receiver.
class Panel {
constructor(name) {
this.name = name;
}
listen(el) {
el.addEventListener('click', () => {
console.log(this.name); // Panel instance
});
}
}
bind, call, and apply
bind returns a new function with this fixed. DOM APIs and timers often need it when passing methods as callbacks.
const handler = button.handleClick.bind(button);
document.addEventListener('click', handler);
Use call and apply when you want to invoke once with a chosen this and arguments. apply takes an array of args; call lists them.
Practical rules
- Prefer regular methods on objects and classes when
thisshould be the instance. - Use arrow functions for inline callbacks when you want lexical
this. - If you see
const self = this, check whether an arrow function reads clearer. - In event handlers,
event.currentTargetis often clearer than fightingthis.
Next: classes and prototypes – what you actually need from object-oriented JavaScript, without pretending it is Java.

