ToolPopToolPop
JavaScript · Lesson 18 of 18

Coding interview patterns: Promise.all, deep clone, curry, EventEmitter

9 min readUpdated 24 Jun 2026

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:

Diagram
rendering diagram...
js
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:

  1. Handle the empty input case first. Most candidates forget and the outer promise hangs forever.
  2. Promise.resolve(p) lifts non-promise values into promises, matching native behaviour.
  3. Assign by index so output order matches input, even though resolution order does not.
  4. First rejection wins. Later resolutions still fire but reject is a no-op after the first call.

Trap: pushing to results instead 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.

js
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.
  • WeakMap tracks visited refs so circular structures do not blow the stack.
  • Arrays and plain objects are handled. Date, Map, Set, typed arrays, RegExp are 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.

js
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);  // 6

fn.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) has length === 0 and 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.

js
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 let and an init function. 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 class had #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:

  1. Explain how X works. Event loop, closures, prototype chain, this, hoisting, microtasks vs macrotasks.
  2. 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

0/3 answered
  1. 1.A correct Promise.all polyfill resolves...
  2. 2.What is currying?
  3. 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?
A.Currying lets you pre-fill some arguments and pass the partially-applied function around. Useful for callbacks, event handlers, and FP-style pipelines where you want reusable specialised versions of a generic function.
Q.What is dependency injection in plain language?
A.Instead of a class constructing the things it needs (like new Database()), the things are passed in from outside. This makes the class testable (you can pass in a fake database) and decouples it from specific implementations.
Chai0/2 done

Watching quietly. Tap me if you want a tip.

JS Playground
console
// hit run to see output

Try this (0 of 2 done)

  1. 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. 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');