What are Closure, Event Loop, Hoisting, and Currying
Four core JS concepts: closures (a function + its remembered lexical scope), the event loop (the scheduler that runs queued async callbacks on the single thread), hoisting (declarations are processed before execution — var/function hoisted, let/const in the temporal dead zone), and currying (transforming f(a,b,c) into f(a)(b)(c) via closures).
A rapid-fire fundamentals question covering four staples. Here's a crisp explanation of each.
Closure
A closure is a function together with the lexical scope it was defined in. The function keeps access to those outer variables even after the outer function has returned.
function makeAdder(x) {
return (y) => x + y; // closes over x
}
const add5 = makeAdder(5);
add5(3); // 8Uses: data privacy, function factories, memoization, event handlers, React hooks.
Event loop
JS is single-threaded. The event loop is the scheduler: async work (timers, fetch, events) is handled by Web APIs; their callbacks queue up; when the call stack is empty, the loop drains all microtasks (Promises) then runs one macrotask (timers/events), repeating.
console.log(1);
setTimeout(() => console.log(2)); // macrotask
Promise.resolve().then(() => console.log(3)); // microtask
console.log(4);
// 1, 4, 3, 2Hoisting
During the creation phase of an execution context, declarations are registered before any code runs:
var→ hoisted, initialized toundefined.functiondeclarations → fully hoisted (callable before their line).let/const→ hoisted but uninitialized — referencing them before declaration throws (the Temporal Dead Zone).
console.log(a); // undefined (var hoisted)
console.log(b); // ReferenceError (let in TDZ)
var a = 1;
let b = 2;Currying
Currying transforms a function of N arguments into N nested functions of one argument each — built on closures.
const curry = (fn) => {
return function curried(...args) {
return args.length >= fn.length
? fn(...args)
: (...more) => curried(...args, ...more);
};
};
const sum = curry((a, b, c) => a + b + c);
sum(1)(2)(3); // 6
sum(1, 2)(3); // 6Uses: partial application, reusable specialized functions, point-free composition.
Senior framing
The thread connecting them: closures are the foundation — currying is closures, hoisting determines when the bindings closures capture exist, and the event loop is why a deferred callback still needs a closure to reach its data. Knowing each individually is junior; articulating that closures underpin three of the four is senior.
Follow-up questions
- •How is currying implemented using closures?
- •What's the Temporal Dead Zone?
- •Why are function declarations hoisted but function expressions aren't?
Common mistakes
- •Saying let/const aren't hoisted — they are, but uninitialized (TDZ).
- •Confusing currying with partial application.
- •Thinking the event loop makes JS multi-threaded.
Edge cases
- •Function declarations hoist above var declarations of the same name.
- •Currying with variadic functions needs an explicit arity or terminator.
Real-world examples
- •Closures in hooks, currying in functional libraries (Ramda, lodash/fp), hoisting bugs with var.