Coding interview patterns: Promise.all, deep clone, curry, EventEmitter
This is the lesson you skim the night before a senior JS interview at Razorpay, Swiggy, or Flipkart. Each pattern below has been asked thousands of times. Write each one once by hand, then again from memory. That is the prep.
Promise.all polyfill
Promise.all takes an iterable of promises and returns a single promise. It resolves with an array of results in input order, or rejects on the first rejection. Fail-fast.
Mental model:
function promiseAll(promises) {
return new Promise((resolve, reject) => {
const results = new Array(promises.length);
let done = 0;
if (promises.length === 0) return resolve(results);
promises.forEach((p, i) => {
Promise.resolve(p).then(val => {
results[i] = val;
done++;
if (done === promises.length) resolve(results);
}, reject);
});
});
}Step-by-step:
- Handle the empty input case first. Most candidates forget and the outer promise hangs forever.
Promise.resolve(p)lifts non-promise values into promises, matching native behaviour.- Assign by index so output order matches input, even though resolution order does not.
- First rejection wins. Later resolutions still fire but
rejectis a no-op after the first call.
Trap: pushing to
resultsinstead of indexing breaks order. Interviewers always check this.
Deep clone for nested objects
Shallow copy via spread or Object.assign only goes one level. For nested state (UPI transaction with nested payer/payee), you need recursion.
function deepClone(value, seen = new WeakMap()) {
if (value === null || typeof value !== "object") return value;
if (seen.has(value)) return seen.get(value); // cycle guard
const out = Array.isArray(value) ? [] : {};
seen.set(value, out);
for (const key of Object.keys(value)) {
out[key] = deepClone(value[key], seen);
}
return out;
}- Primitives are returned as-is.
WeakMaptracks visited refs so circular structures do not blow the stack.- Arrays and plain objects are handled.
Date,Map,Set, typed arrays,RegExpare not. Mention that out loud.
The modern answer in 2026 is structuredClone(value). It is native, handles cycles, Map, Set, Date, ArrayBuffer, even transferables. Use it in real code. Write the recursive version on the whiteboard because that is what interviewers want to see.
Curry
Curry transforms sum(a, b, c) into sum(a)(b)(c). Used in functional libraries and configuration helpers.
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return (...more) => curried.apply(this, [...args, ...more]);
};
}
const sum = curry((a, b, c) => a + b + c);
sum(1)(2)(3); // 6
sum(1, 2)(3); // 6
sum(1)(2, 3); // 6fn.length is the declared arity of the function. When we have collected enough, we call. Otherwise we return another function that gathers more.
Trap: curry assumes a fixed arity. A variadic function (
...args) haslength === 0and curry will call immediately. Mention this caveat in the interview.
EventEmitter: on, emit, off, once
Every Node module under the hood uses one. Writing it shows you understand the observer pattern.
class EventEmitter {
#handlers = new Map();
on(event, fn) {
if (!this.#handlers.has(event)) this.#handlers.set(event, new Set());
this.#handlers.get(event).add(fn);
return () => this.off(event, fn); // unsubscribe handle
}
off(event, fn) { this.#handlers.get(event)?.delete(fn); }
emit(event, ...args) {
this.#handlers.get(event)?.forEach(fn => fn(...args));
}
once(event, fn) {
const wrap = (...args) => { this.off(event, wrap); fn(...args); };
return this.on(event, wrap);
}
}Set as the handler bucket gives O(1) off and prevents duplicate subscriptions. once wraps the handler in a self-removing version. Returning an unsubscribe function from on is the modern pattern (React hooks expect it).
Security in one paragraph each
XSS (Cross-Site Scripting). Attacker injects JS into your page, usually via unescaped user input rendered as HTML. The browser runs it as if you wrote it, reads cookies, hijacks sessions. Fix: never use innerHTML with user data. React's JSX escapes by default. Set a strict Content Security Policy.
CSRF (Cross-Site Request Forgery). A malicious site tricks the user's browser into sending an authenticated request to your site (the cookie rides along). Fix: SameSite=Lax or Strict cookies, CSRF tokens on state-changing requests, do not rely on cookies alone for auth.
CORS (Cross-Origin Resource Sharing). Not an attack, a protection. Browsers block JS from reading responses from other origins unless the server opts in with Access-Control-Allow-Origin. Most "CORS errors" in dev mean your backend forgot the header for OPTIONS preflight requests.
Design patterns, one line each
- Singleton. One instance shared across the app. Use a module-level
letand aninitfunction. Do not overuse. - Factory. A function that returns objects, hiding the construction details.
createUser(role)returns the right subtype. - Observer. One-to-many notify. EventEmitter is the canonical example. React state and RxJS are both observers underneath.
- Module. Encapsulation via closure or ES modules. Private state, public API. The original JS way to hide things before
classhad#private. - Dependency Injection. Pass dependencies in (DB client, logger, clock) instead of constructing them inside. Makes code testable. Constructor injection is the simplest form.
Interview patterns
- Fail-fast
- Promise.all rejects on the first failure, not waiting for the others.
- structuredClone
- Built-in deep clone (ES2022). Handles cycles, Map, Set, Date. Use in real code.
- Currying
- Transforming f(a,b,c) into f(a)(b)(c). Relies on closures.
- Observer pattern
- Subject keeps a list of subscribers and notifies them on change.
- Preflight
- OPTIONS request the browser sends before a non-simple cross-origin request to check CORS.
The shape of the interview
The senior JS interview is mostly two things, repeated:
- Explain how X works. Event loop, closures, prototype chain,
this, hoisting, microtasks vs macrotasks. - Implement Y from scratch.
Promise.all, debounce, throttle, deep clone, curry, EventEmitter.
Practise both. Talk while you type. Narrate edge cases before the interviewer asks.
Senior rule: knowing the answer is not enough. You have to write it on the whiteboard while explaining the steps out loud. That is the bar.
Quick quiz, prove you got it
- 1.A correct Promise.all polyfill resolves...
- 2.What is currying?
- 3.Which best describes Dependency Injection?
Interview insights
The senior coding round
- Always start by clarifying inputs and edge cases (empty arrays, null, async)
- Write the obvious version first, get it working, THEN optimise
- Talk through your thinking out loud, even when stuck
- For "implement X" questions, know: Promise.all, debounce, throttle, deep clone, curry, memoize, EventEmitter
takeaway: Knowing the answer is half. Explaining it cleanly while writing is the other half.
Tricky questions they will ask
Q.In your Promise.all polyfill, why must you initialise the results array with the correct length?›
A.Because promises resolve in any order. You assign by index: results[i] = value. If you push() instead, the order matches resolution order, not input order, and you fail the order guarantee.
Q.How is currying different from partial application?›
A.Currying produces a chain of strictly single-arg functions: f(a)(b)(c). Partial application fixes some arguments and returns a function that takes the rest, but the returned function can take multiple args at once: f.bind(null, a) then f(b, c). Currying is one special form of partial application.
Free tools you can use while you learn
Common questions
Q.How is curry useful in real code?›
Q.What is dependency injection in plain language?›
Watching quietly. Tap me if you want a tip.
Try this (0 of 2 done)
- 1
Implement a curry function. curry(sum)(1)(2)(3) should equal 6 where sum(a,b,c) returns a+b+c.
show answer
function curry(fn) { return function curried(...args) { if (args.length >= fn.length) return fn(...args); return (...next) => curried(...args, ...next); }; } const sum = (a,b,c) => a + b + c; console.log(curry(sum)(1)(2)(3)); - 2
Build a tiny EventEmitter with on() and emit(). Subscribe to "order", emit it with payload "delivered".
show answer
class EventEmitter { constructor() { this.events = {}; } on(e, cb) { (this.events[e] ||= []).push(cb); } emit(e, data) { (this.events[e] || []).forEach(cb => cb(data)); } } const bus = new EventEmitter(); bus.on('order', (s) => console.log('status:', s)); bus.emit('order', 'delivered');