AI InterviewSearch…Search problems, topics, or pages…Ctrl KGet started
Skip to content

16 Interview idioms / Lesson 4 of 5

Implement the built-in — the reimplementation questions and the details that decide them

14 min · +10 XP · 2 exercises · 5 questions

Read the text14 min
Exercises0/2
Quiz5 q

Quick answer

How do you implement Promise.all in JavaScript?

Return a new promise that wraps each input with Promise.resolve, attaches a then handler, and stores each value at its own index, so the results keep input order rather than completion order. A counter of pending items resolves the outer promise when it reaches zero, the first rejection rejects it, and an empty input resolves [] immediately.

"Implement Promise.all." "Write bind." "How would you implement Array.prototype.reduce?" These questions test whether you understand the semantics of things you use every day — not whether you can recite them. The trap is that the obvious ten-line version is wrong in ways the interviewer knows about: bind must work with new, map must skip holes, reduce must throw on an empty array without a seed, Promise.all must preserve order and reject on the first failure, JSON.stringify must drop undefined in objects but write null in arrays. This lesson lists the questions that recur, the spec details each one hinges on, and the shape of a correct answer; the exercises and the checkpoint have you write them.

The recurring list

AskThe detail that decides it
call/apply/bindSet this via a temporary symbol-keyed property (or Reflect.apply); bind pre-applies arguments and is ignored as this when the bound function is called with new — detect via this instanceof bound or new.target
newCreate an object whose prototype is Ctor.prototype, call Ctor with it as this, return the object unless the constructor returned an object
instanceofWalk Object.getPrototypeOf from the value until null, comparing to Ctor.prototype; primitives are false; honour Symbol.hasInstance if asked
Object.createAn object with the given prototype (null allowed) plus optional descriptors
Array.prototype.map/filter/forEachIterate 0…length−1, skip holes (i in arr), pass (value, index, array), honour thisArg; map preserves length and holes
reduceWith no initial value start from the first present element; throw TypeError on an empty array without a seed
flat(depth)Recursive with depth − 1, Infinity allowed, holes removed, depth default 1; flatMap is map then flat(1)
includes versus indexOfincludes uses SameValueZero (finds NaN); indexOf uses === (never finds NaN)
Promise.allResult array in input order, resolve when the counter hits length, reject on the first rejection, non-promise inputs are wrapped (Promise.resolve), an empty input resolves [] immediately
Promise.allSettled / race / anyallSettled never rejects — `{status, valuereason}; race settles with the first to settle; any rejects with an AggregateError` only when all reject
A Promise classState machine (pending → fulfilled/rejected, once); then returns a new promise; handlers run asynchronously (microtask) even when already settled; a handler's return value resolves the next promise, a thrown error rejects it, a returned thenable is adopted
JSON.stringifyundefined/functions/symbols are omitted as object values and become null in arrays; NaN/Infinity → null; toJSON honoured (Dates); strings escaped; cycles throw TypeError; replacer function/array and indent
debounce/throttleShared timer across calls; trailing versus leading; cancel; preserve this and arguments
EventEmitteron/off/once/emit; copy the listener list before emitting so off during emit is safe
LRU cacheMap insertion order: delete + re-set on access; evict map.keys().next().value
deepClonePrimitives, arrays, plain objects, Date, RegExp, Map, Set; a WeakMap of seen objects for cycles and shared references; class instances lose their prototype unless you Object.create(Object.getPrototypeOf(x))
deepEqualSame type; NaN equals NaN; arrays by length and elements; objects by own key sets; Date by time; Map/Set by entries; cycles via a visited pair set if asked
curry/memoize/once/composeArity via fn.length; memo keys for multiple arguments; once caches the first result

Worked shape: bind

Function.prototype.myBind = function (thisArg, ...preset) {
  const target = this;
  function bound(...args) {
    // Called with `new`? Then `this` is the fresh instance and thisArg is ignored.
    const useNew = this instanceof bound;
    return target.apply(useNew ? this : thisArg, [...preset, ...args]);
  }
  bound.prototype = Object.create(target.prototype ?? null);   // so instanceof and `new` keep working
  return bound;
};

Interviewers probe exactly the new case and the partial-application case; mention both before they ask.

Worked shape: Promise.all

function all(iterable) {
  return new Promise((resolve, reject) => {
    const items = [...iterable];
    const results = new Array(items.length);
    let pending = items.length;
    if (pending === 0) return resolve(results);
    items.forEach((item, i) => {
      Promise.resolve(item).then((value) => {
        results[i] = value;                      // index, not push order
        if (--pending === 0) resolve(results);
      }, reject);                                // first rejection wins; later ones are ignored by the state machine
    });
  });
}

The two things people get wrong: pushing in completion order (breaks input order) and forgetting the empty case (never resolves).

Worked shape: a Promise

class MyPromise {
  #state = "pending"; #value; #handlers = [];
  constructor(executor) {
    const settle = (state) => (value) => { if (this.#state !== "pending") return; this.#state = state; this.#value = value; this.#handlers.forEach((h) => h()); this.#handlers = []; };
    try { executor(settle("fulfilled"), settle("rejected")); } catch (e) { settle("rejected")(e); }
  }
  then(onOk, onErr) {
    return new MyPromise((resolve, reject) => {
      const run = () => queueMicrotask(() => {                                        // always asynchronous
        const handler = this.#state === "fulfilled" ? onOk : onErr;
        if (typeof handler !== "function") return this.#state === "fulfilled" ? resolve(this.#value) : reject(this.#value);   // pass-through
        try { const out = handler(this.#value); out && typeof out.then === "function" ? out.then(resolve, reject) : resolve(out); }   // adopt thenables
        catch (e) { reject(e); }
      });
      this.#state === "pending" ? this.#handlers.push(run) : run();
    });
  }
  catch(onErr) { return this.then(undefined, onErr); }
}

The points that score: settling is one-shot; then returns a new promise; handlers run in a microtask even when the promise is already settled; a missing handler passes the value/reason through; returned thenables are adopted. resolve(thenable) inside the executor should adopt too — mention it if you skip it.

Worked shape: reduce with holes and the empty case

Array.prototype.myReduce = function (fn, ...seed) {
  let i = 0, acc;
  if (seed.length) acc = seed[0];
  else { while (i < this.length && !(i in this)) i++; if (i >= this.length) throw new TypeError("Reduce of empty array with no initial value"); acc = this[i++]; }
  for (; i < this.length; i++) if (i in this) acc = fn(acc, this[i], i, this);
  return acc;
};

Using rest for the seed distinguishes "no seed" from "seed is undefined" — the arity check the spec makes with arguments.length.

How to answer these live

  1. Say the contract first — what the built-in guarantees — in three or four bullets, including the edge case you know the interviewer is thinking of.
  2. Write the simple path.
  3. Add the edge cases one at a time, naming each ("now the new case").
  4. Test with two calls: the normal one and the edge one.

If you do not know a detail, say what you would check ("I believe map skips holes; I'd confirm on MDN"). Guessing confidently and wrong is the worst outcome.

Common mistakes

  • bind that ignores new; new that ignores an object returned by the constructor.
  • map/filter on arr.length without i in arr; reduce seeded with undefined instead of checking arity.
  • Promise.all pushing in completion order or never resolving on []; then that runs handlers synchronously.
  • JSON.stringify writing undefined or leaving NaN; forgetting toJSON.
  • deepClone without a seen-map (infinite recursion on cycles); deepEqual that fails on NaN or compares Dates by identity.

What the interviewer is listening for

  • Whether you state the contract before coding, and whether it includes the famous edge case.
  • Whether the asynchronous ones stay asynchronous and one-shot.
  • Whether the array ones respect holes, thisArg and the callback signature.
  • Whether you test the edge case unprompted.

Key takeaways

  • The recurring reimplementation questions each hinge on one or two spec details — know the detail, not just the sketch.
  • bind and new; map and holes; reduce and the empty array; Promise.all and order/empty; then and microtasks/adoption; JSON.stringify and undefined/NaN/toJSON/cycles.
  • Answer in the order contract → simple path → edge cases named one by one → two tests.
  • Say what you would verify rather than guessing a detail.

Common questions

How do you implement bind in JavaScript?

Return a function that calls the target with apply, passing the bound this and the preset arguments followed by the new ones. The detail interviewers probe: when the bound function is called with new, the bound this is ignored — detect it with this instanceof bound, and link the bound function's prototype to the target's.

How do you write a Promise from scratch?

As a state machine that settles once, from pending to fulfilled or rejected. then returns a new promise; its handlers run in a microtask even when the promise has already settled; a missing handler passes the value or reason through; a thrown error rejects the next promise; and a returned thenable is adopted.

Why does reduce throw on an empty array?

With no initial value, reduce starts from the first present element, and an empty array has none, so it throws TypeError: Reduce of empty array with no initial value. A correct reimplementation checks whether a seed was passed at all — the argument count — rather than whether it is undefined.

What does JSON.stringify do with undefined, functions and NaN?

As object values, undefined, functions and symbols are left out; inside arrays they become null. NaN and Infinity are written as null, a toJSON method such as Date's is honoured, and a circular reference throws a TypeError.

What is the difference between includes and indexOf?

includes compares with SameValueZero, so [NaN].includes(NaN) is true; indexOf uses strict equality, so [NaN].indexOf(NaN) is -1. Both treat +0 and -0 as equal; includes returns a boolean, indexOf the position or -1.

Mark as readSign in to keep your place.

JavaScript Polyfill Interview Questions: Promise.all and Bind

How to implement Promise.all, bind, new, reduce, a Promise class and JSON.stringify for JavaScript interviews, and the spec detail each answer hinges on.

How do you implement Promise.all in JavaScript?

Return a new promise that wraps each input with Promise.resolve, attaches a then handler, and stores each value at its own index, so the results keep input order rather than completion order. A counter of pending items resolves the outer promise when it reaches zero, the first rejection rejects it, and an empty input resolves [] immediately.

Lesson

"Implement Promise.all." "Write bind." "How would you implement Array.prototype.reduce?" These questions test whether you understand the semantics of things you use every day — not whether you can recite them. The trap is that the obvious ten-line version is wrong in ways the interviewer knows about: bind must work with new, map must skip holes, reduce must throw on an empty array without a seed, Promise.all must preserve order and reject on the first failure, JSON.stringify must drop undefined in objects but write null in arrays. This lesson lists the questions that recur, the spec details each one hinges on, and the shape of a correct answer; the exercises and the checkpoint have you write them.

The recurring list

AskThe detail that decides it
call/apply/bindSet this via a temporary symbol-keyed property (or Reflect.apply); bind pre-applies arguments and is ignored as this when the bound function is called with new — detect via this instanceof bound or new.target
newCreate an object whose prototype is Ctor.prototype, call Ctor with it as this, return the object unless the constructor returned an object
instanceofWalk Object.getPrototypeOf from the value until null, comparing to Ctor.prototype; primitives are false; honour Symbol.hasInstance if asked
Object.createAn object with the given prototype (null allowed) plus optional descriptors
Array.prototype.map/filter/forEachIterate 0…length−1, skip holes (i in arr), pass (value, index, array), honour thisArg; map preserves length and holes
reduceWith no initial value start from the first present element; throw TypeError on an empty array without a seed
flat(depth)Recursive with depth − 1, Infinity allowed, holes removed, depth default 1; flatMap is map then flat(1)
includes versus indexOfincludes uses SameValueZero (finds NaN); indexOf uses === (never finds NaN)
Promise.allResult array in input order, resolve when the counter hits length, reject on the first rejection, non-promise inputs are wrapped (Promise.resolve), an empty input resolves [] immediately
Promise.allSettled / race / anyallSettled never rejects — `{status, valuereason}; race settles with the first to settle; any rejects with an AggregateError` only when all reject
A Promise classState machine (pending → fulfilled/rejected, once); then returns a new promise; handlers run asynchronously (microtask) even when already settled; a handler's return value resolves the next promise, a thrown error rejects it, a returned thenable is adopted
JSON.stringifyundefined/functions/symbols are omitted as object values and become null in arrays; NaN/Infinity → null; toJSON honoured (Dates); strings escaped; cycles throw TypeError; replacer function/array and indent
debounce/throttleShared timer across calls; trailing versus leading; cancel; preserve this and arguments
EventEmitteron/off/once/emit; copy the listener list before emitting so off during emit is safe
LRU cacheMap insertion order: delete + re-set on access; evict map.keys().next().value
deepClonePrimitives, arrays, plain objects, Date, RegExp, Map, Set; a WeakMap of seen objects for cycles and shared references; class instances lose their prototype unless you Object.create(Object.getPrototypeOf(x))
deepEqualSame type; NaN equals NaN; arrays by length and elements; objects by own key sets; Date by time; Map/Set by entries; cycles via a visited pair set if asked
curry/memoize/once/composeArity via fn.length; memo keys for multiple arguments; once caches the first result

Worked shape: bind

Function.prototype.myBind = function (thisArg, ...preset) {
  const target = this;
  function bound(...args) {
    // Called with `new`? Then `this` is the fresh instance and thisArg is ignored.
    const useNew = this instanceof bound;
    return target.apply(useNew ? this : thisArg, [...preset, ...args]);
  }
  bound.prototype = Object.create(target.prototype ?? null);   // so instanceof and `new` keep working
  return bound;
};

Interviewers probe exactly the new case and the partial-application case; mention both before they ask.

Worked shape: Promise.all

function all(iterable) {
  return new Promise((resolve, reject) => {
    const items = [...iterable];
    const results = new Array(items.length);
    let pending = items.length;
    if (pending === 0) return resolve(results);
    items.forEach((item, i) => {
      Promise.resolve(item).then((value) => {
        results[i] = value;                      // index, not push order
        if (--pending === 0) resolve(results);
      }, reject);                                // first rejection wins; later ones are ignored by the state machine
    });
  });
}

The two things people get wrong: pushing in completion order (breaks input order) and forgetting the empty case (never resolves).

Worked shape: a Promise

class MyPromise {
  #state = "pending"; #value; #handlers = [];
  constructor(executor) {
    const settle = (state) => (value) => { if (this.#state !== "pending") return; this.#state = state; this.#value = value; this.#handlers.forEach((h) => h()); this.#handlers = []; };
    try { executor(settle("fulfilled"), settle("rejected")); } catch (e) { settle("rejected")(e); }
  }
  then(onOk, onErr) {
    return new MyPromise((resolve, reject) => {
      const run = () => queueMicrotask(() => {                                        // always asynchronous
        const handler = this.#state === "fulfilled" ? onOk : onErr;
        if (typeof handler !== "function") return this.#state === "fulfilled" ? resolve(this.#value) : reject(this.#value);   // pass-through
        try { const out = handler(this.#value); out && typeof out.then === "function" ? out.then(resolve, reject) : resolve(out); }   // adopt thenables
        catch (e) { reject(e); }
      });
      this.#state === "pending" ? this.#handlers.push(run) : run();
    });
  }
  catch(onErr) { return this.then(undefined, onErr); }
}

The points that score: settling is one-shot; then returns a new promise; handlers run in a microtask even when the promise is already settled; a missing handler passes the value/reason through; returned thenables are adopted. resolve(thenable) inside the executor should adopt too — mention it if you skip it.

Worked shape: reduce with holes and the empty case

Array.prototype.myReduce = function (fn, ...seed) {
  let i = 0, acc;
  if (seed.length) acc = seed[0];
  else { while (i < this.length && !(i in this)) i++; if (i >= this.length) throw new TypeError("Reduce of empty array with no initial value"); acc = this[i++]; }
  for (; i < this.length; i++) if (i in this) acc = fn(acc, this[i], i, this);
  return acc;
};

Using rest for the seed distinguishes "no seed" from "seed is undefined" — the arity check the spec makes with arguments.length.

How to answer these live

  1. Say the contract first — what the built-in guarantees — in three or four bullets, including the edge case you know the interviewer is thinking of.
  2. Write the simple path.
  3. Add the edge cases one at a time, naming each ("now the new case").
  4. Test with two calls: the normal one and the edge one.

If you do not know a detail, say what you would check ("I believe map skips holes; I'd confirm on MDN"). Guessing confidently and wrong is the worst outcome.

Common mistakes

  • bind that ignores new; new that ignores an object returned by the constructor.
  • map/filter on arr.length without i in arr; reduce seeded with undefined instead of checking arity.
  • Promise.all pushing in completion order or never resolving on []; then that runs handlers synchronously.
  • JSON.stringify writing undefined or leaving NaN; forgetting toJSON.
  • deepClone without a seen-map (infinite recursion on cycles); deepEqual that fails on NaN or compares Dates by identity.

What the interviewer is listening for

  • Whether you state the contract before coding, and whether it includes the famous edge case.
  • Whether the asynchronous ones stay asynchronous and one-shot.
  • Whether the array ones respect holes, thisArg and the callback signature.
  • Whether you test the edge case unprompted.

Key takeaways

  • The recurring reimplementation questions each hinge on one or two spec details — know the detail, not just the sketch.
  • bind and new; map and holes; reduce and the empty array; Promise.all and order/empty; then and microtasks/adoption; JSON.stringify and undefined/NaN/toJSON/cycles.
  • Answer in the order contract → simple path → edge cases named one by one → two tests.
  • Say what you would verify rather than guessing a detail.

Common questions

How do you implement bind in JavaScript?

Return a function that calls the target with apply, passing the bound this and the preset arguments followed by the new ones. The detail interviewers probe: when the bound function is called with new, the bound this is ignored — detect it with this instanceof bound, and link the bound function's prototype to the target's.

How do you write a Promise from scratch?

As a state machine that settles once, from pending to fulfilled or rejected. then returns a new promise; its handlers run in a microtask even when the promise has already settled; a missing handler passes the value or reason through; a thrown error rejects the next promise; and a returned thenable is adopted.

Why does reduce throw on an empty array?

With no initial value, reduce starts from the first present element, and an empty array has none, so it throws TypeError: Reduce of empty array with no initial value. A correct reimplementation checks whether a seed was passed at all — the argument count — rather than whether it is undefined.

What does JSON.stringify do with undefined, functions and NaN?

As object values, undefined, functions and symbols are left out; inside arrays they become null. NaN and Infinity are written as null, a toJSON method such as Date's is honoured, and a circular reference throws a TypeError.

What is the difference between includes and indexOf?

includes compares with SameValueZero, so [NaN].includes(NaN) is true; indexOf uses strict equality, so [NaN].indexOf(NaN) is -1. Both treat +0 and -0 as equal; includes returns a boolean, indexOf the position or -1.

Exercises

bind, call, apply, new and instanceof by hand

Implement Function.prototype.myCall/myApply (bind this through a temporary symbol-keyed method), myBind (pre-applied arguments and the new case — a bound function called with new ignores the bound this and keeps the prototype chain), myNew(Ctor, …args) (an object returned by the constructor replaces this) and myInstanceOf(value, Ctor) (walk the prototype chain; primitives and null are false). The starter's checks compare each against the native behaviour; stdin lists check names or all. Print ok <check>: <detail> match=true per check and <n>/<total> match the native behaviour.

Example (all) →

ok call: hi, Ada! match=true
ok apply: 9 match=true
ok bind-partial: add(10)(5)=15 match=true
ok bind-this: n=2 match=true
ok bind-new: {x:1,y:2} instanceof Point=true sum=3 match=true
ok new-plain: {x:1,y:2} sum=3 match=true
ok new-returns-object: {"custom":true} match=true
ok instanceof: dog->Dog,Animal,Object,Array | [],5,null,nullProto->Object = true,true,true,false,true,false,false,false match=true
8/8 match the native behaviour

The array methods from scratch

Line 1 lists elements — numbers, NaN, words, JSON arrays like [3,[4,[5]]] and _ for a hole. Implement myForEach, myMap, myFilter, myReduce, myFlat(depth = 1), myFlatMap, myIncludes and myIndexOf on Array.prototype with the spec details: holes are skipped by the callbacks (map keeps them as holes), thisArg is honoured, reduce without a seed starts from the first present element and throws TypeError on an empty array, includes uses SameValueZero (finds NaN, reads a hole as undefined) while indexOf uses === and skips holes. The starter's show renders holes as <hole>; print each comparison as <label>: <mine> match=<bool>.

Example: 1 _ 2 NaN hello [3,[4,[5]]] _ → map x2: [2,<hole>,4,NaN,"hello",[3,[4,[5]]],<hole>] match=true, flat(Infinity): [1,2,NaN,"hello",3,4,5] match=true, includes(NaN): true match=true, indexOf(NaN): -1 match=true, includes(undefined) (a hole reads as undefined): true match=true.

In this module: Interview idioms

← The idiom sheet — the one-liners worth knowing by heart, and the traps inside them · Theory drill — the questions, the two-sentence answers, and where each one goes deeper →