Skip to main content

Common Coding Tasks

At a live-coding round you are asked to re-implement a built-in or a small utility: bind, Promise.all, debounce, a deep copy, an event emitter, an LRU cache. The task checks closures, this, promises and attention to edge cases, not memorized code. Clarify the contract first (arguments, this, errors, return value), write the simple version, then name the edge cases and the complexity. This page is an index: each row links to the page that explains the topic and shows the solution.

Details​

Functions and this​

TaskWhat it checksSolution
bindClosures, this, fixed arguments, new on a bound functionbind, call and apply, follow-up questions
call / applyHow this is set by a method callBelow
debounce / throttleTimers, closures, passing this and arguments throughDebounce and Throttle
memoizeA cache in a closure, the cache key, pure functionsFunctional Programming, follow-up questions
onceState in a closure, keeping the first resultBelow
curry, compose, sum(1)(2)(3)Closures, fn.length, the order of applicationFunctional Programming, Example
A retry wrapperHigher-order functions, async / await in a loopDesign Patterns, the Decorator example

Promises​

TaskWhat it checksSolution
Promise.allResult order, fail-fast, an empty input, non-promise valuesPromises, follow-up questions
allSettled / raceThe difference between the combinatorsBelow
A request with a timeoutAbortController, AbortSignal.timeoutPromises, "More examples"
Promisify a callback APInew Promise, resolve / reject in the callbackBelow
Run promises one by onefor...of with await vs Promise.allPromises, "More examples"
At most N tasks at a timeA queue of tasks, workers, keeping the result orderBelow
"What does this print?"Microtasks vs tasks, the synchronous executorEvent Loop, Promises

Objects and Arrays​

TaskWhat it checksSolution
Deep copyRecursion, cycles, Date / Map / Set; knowing structuredCloneObjects for the built-ins, below by hand
Deep equalRecursion, Object.is, keys and prototypesBelow
flatRecursion with depthArrays for the built-in, below by hand
var in a loop with timersClosures and block scopeClosures, follow-up questions
this in a callbackThe rules of thisThe this Keyword
map / filter / reduce by handCallbacks, the initial value, an empty arrayBelow
new by handThe prototype chain, a constructor that returns an objectPrototypes, the four steps

Data Structures and Algorithms​

TaskWhat it checksSolution
Event emitter (on / off / once / emit)A Map of listener sets, unsubscribe, emit while changingBelow; Observer in Design Patterns
LRU cacheO(1) get and set, the insertion order of MapBelow
Binary search, duplicates, BFS, FibonacciBig O, choosing a structure, memoizationAlgorithms and Data Structures

React​

TaskWhat it checksSolution
Search input with debounceA stable debounced function, cleanup on unmountDebounce and Throttle, "In React"
Fetching in an effectRace conditions, cleanup, loading and error statesSide Effects
A custom hook (useToggle, useDebounce)Extracting logic, rules of hooksHooks
Tabs, accordionCompound components, contextComposition Patterns
A list with add / removekey, immutable updatesLists and key, State

Example​

Solutions to the tasks not covered on other pages.

call and apply​

Solution
Function.prototype.myCall = function (thisArg, ...args) {
// emulates non-strict `this`: null / undefined → globalThis, primitives → objects
const context = thisArg == null ? globalThis : Object(thisArg);
const key = Symbol('fn'); // a unique key does not overwrite own properties
context[key] = this;
try {
return context[key](...args); // a method call: `this` is `context`
} finally {
delete context[key];
}
};

Function.prototype.myApply = function (thisArg, args = []) {
return this.myCall(thisArg, ...args);
};

function greet(greeting, mark) {
return `${greeting}, ${this.name}${mark}`;
}
console.log(greet.myCall({ name: 'Ada' }, 'Hi', '!')); // Hi, Ada!
console.log(greet.myApply({ name: 'Bob' }, ['Hello', '?'])); // Hello, Bob?

once​

Solution
function once(fn) {
let called = false;
let result;
return function (...args) {
if (!called) {
called = true;
result = fn.apply(this, args);
}
return result;
};
}

const init = once(() => {
console.log('init');
return 42;
});
console.log(init(), init()); // init, then 42 42

allSettled and race​

Solution
function promiseAllSettled(iterable) {
return Promise.all(
[...iterable].map(
(item) =>
Promise.resolve(item).then(
(value) => ({ status: 'fulfilled', value }),
(reason) => ({ status: 'rejected', reason })
)
)
);
}

function promiseRace(iterable) {
return new Promise((resolve, reject) => {
for (const item of iterable) {
// the first to settle wins; later calls to resolve / reject are ignored
Promise.resolve(item).then(resolve, reject);
}
});
}

const fail = Promise.reject(new Error('fail'));
console.log(await promiseAllSettled([1, fail]));
// [{ status: 'fulfilled', value: 1 }, { status: 'rejected', reason: Error: fail }]

const wait = (ms, value) =>
new Promise((resolve) => setTimeout(resolve, ms, value));
console.log(await promiseRace([wait(100, 'slow'), wait(10, 'fast')])); // fast

promisify​

Solution
function promisify(fn) {
return function (...args) {
return new Promise((resolve, reject) => {
// Node.js-style callback: the error first, then the result
fn.call(this, ...args, (error, result) => {
if (error) reject(error);
else resolve(result);
});
});
};
}

function readConfig(name, callback) {
setTimeout(() => {
if (name === 'missing') callback(new Error('Not found'));
else callback(null, { name });
}, 10);
}

const readConfigAsync = promisify(readConfig);
console.log(await readConfigAsync('app')); // { name: 'app' }
await readConfigAsync('missing').catch((error) => console.log(error.message)); // Not found

Concurrency Limit​

Run async tasks with at most limit of them in progress; the results keep the order of the tasks.

Solution
async function runWithLimit(tasks, limit) {
const results = new Array(tasks.length);
let next = 0;

async function worker() {
while (next < tasks.length) {
const index = next++; // synchronous, so two workers never take one task
results[index] = await tasks[index]();
}
}

const workers = Array.from({ length: Math.min(limit, tasks.length) }, worker);
await Promise.all(workers); // rejects on the first failed task
return results;
}

const wait = (ms, value) =>
new Promise((resolve) => setTimeout(resolve, ms, value));
const tasks = [300, 100, 200, 100].map((ms, i) => () => wait(ms, i));

console.log(await runWithLimit(tasks, 2)); // [0, 1, 2, 3] in ~400 ms, not 700

Deep Copy​

In real code, structuredClone. By hand:

Solution
function deepClone(value, seen = new WeakMap()) {
if (value === null || typeof value !== 'object') return value; // primitives, functions
if (seen.has(value)) return seen.get(value); // a cycle: reuse the copy
if (value instanceof Date) return new Date(value);
if (value instanceof Map) {
const copy = new Map();
seen.set(value, copy);
value.forEach((v, k) => copy.set(deepClone(k, seen), deepClone(v, seen)));
return copy;
}
if (value instanceof Set) {
const copy = new Set();
seen.set(value, copy);
value.forEach((v) => copy.add(deepClone(v, seen)));
return copy;
}

const copy = Array.isArray(value)
? []
: Object.create(Object.getPrototypeOf(value));
seen.set(value, copy);
for (const key of Reflect.ownKeys(value)) {
copy[key] = deepClone(value[key], seen);
}
return copy;
}

const original = { date: new Date(0), tags: new Set(['a']), nested: { n: 1 } };
original.self = original;
const copy = deepClone(original);
console.log(copy.nested !== original.nested, copy.self === copy); // true true
console.log(copy.date instanceof Date, copy.tags.has('a')); // true true

Deep Equal​

Solution
function deepEqual(a, b) {
if (Object.is(a, b)) return true; // primitives, the same reference, NaN
if (typeof a !== 'object' || typeof b !== 'object' || !a || !b) return false;
if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) return false;

const keys = Object.keys(a);
if (keys.length !== Object.keys(b).length) return false;
return keys.every(
(key) => Object.hasOwn(b, key) && deepEqual(a[key], b[key])
);
}

console.log(deepEqual({ a: [1, { b: 2 }] }, { a: [1, { b: 2 }] })); // true
console.log(deepEqual({ a: 1 }, { a: 1, b: undefined })); // false
console.log(deepEqual([1, 2], { 0: 1, 1: 2 })); // false: different prototypes
console.log(deepEqual(NaN, NaN), deepEqual(null, {})); // true false
// Not handled: Date, Map, Set (no own keys, so any two are "equal") and cycles

flat​

Solution
function flat(array, depth = 1) {
const result = [];
for (const item of array) {
if (Array.isArray(item) && depth > 0) {
result.push(...flat(item, depth - 1));
} else {
result.push(item);
}
}
return result;
}

console.log(flat([1, [2, [3, [4]]]])); // [1, 2, [3, [4]]]
console.log(flat([1, [2, [3, [4]]]], Infinity)); // [1, 2, 3, 4]

reduce​

map and filter follow the same pattern: a loop that calls the callback with (item, index, array).

Solution
Array.prototype.myReduce = function (callback, ...initial) {
if (this.length === 0 && initial.length === 0) {
throw new TypeError('Reduce of empty array with no initial value');
}
let index = 0;
let accumulator = initial.length > 0 ? initial[0] : this[index++];
for (; index < this.length; index++) {
accumulator = callback(accumulator, this[index], index, this);
}
return accumulator;
};

console.log([1, 2, 3].myReduce((sum, n) => sum + n)); // 6
console.log([1, 2, 3].myReduce((sum, n) => sum + n, 10)); // 16
console.log([[1], [2]].myReduce((all, item) => all.concat(item), [])); // [1, 2]

Event Emitter​

Solution
class EventEmitter {
#listeners = new Map(); // event name → Set of listeners

on(event, listener) {
if (!this.#listeners.has(event)) this.#listeners.set(event, new Set());
this.#listeners.get(event).add(listener);
return () => this.off(event, listener); // unsubscribe
}

off(event, listener) {
this.#listeners.get(event)?.delete(listener);
}

once(event, listener) {
const off = this.on(event, (...args) => {
off();
listener(...args);
});
return off;
}

emit(event, ...args) {
// a copy: listeners added or removed during emit don't affect this call
for (const listener of [...(this.#listeners.get(event) ?? [])]) {
listener(...args);
}
}
}

const emitter = new EventEmitter();
const unsubscribe = emitter.on('message', (text) => console.log('on:', text));
emitter.once('message', (text) => console.log('once:', text));
emitter.emit('message', 'hi'); // on: hi, once: hi
unsubscribe();
emitter.emit('message', 'again'); // nothing

LRU Cache​

A cache of fixed size that evicts the least recently used entry. A Map iterates keys in insertion order, so deleting and re-inserting a key moves it to the end, and the first key is the oldest.

Solution
class LRUCache {
#capacity;
#map = new Map(); // the first key is the least recently used

constructor(capacity) {
this.#capacity = capacity;
}

get(key) {
if (!this.#map.has(key)) return undefined;
const value = this.#map.get(key);
this.#map.delete(key); // move to the end: now the most recent
this.#map.set(key, value);
return value;
}

set(key, value) {
this.#map.delete(key);
this.#map.set(key, value);
if (this.#map.size > this.#capacity) {
this.#map.delete(this.#map.keys().next().value); // evict the oldest
}
}
}

const cache = new LRUCache(2);
cache.set('a', 1);
cache.set('b', 2);
cache.get('a'); // 'a' is now the most recent
cache.set('c', 3); // evicts 'b'
console.log(cache.get('b'), cache.get('a'), cache.get('c')); // undefined 1 3

Follow-up Questions​

  • How would you test it? Name the cases out loud: an empty input, one item, an error, this and arguments passed through, the order of async results; for timers, fake timers (vi.useFakeTimers()).
  • Why a Symbol in myCall? A unique key cannot overwrite a property the object already has; a string key like fn could.
  • What changes in myCall for strict-mode code? Native call passes this as is: null stays null and primitives are not boxed. The emulation can't do that, because it needs an object to attach the method to.
  • What is the complexity of the LRU cache? O(1) for get and set: Map lookup, delete and insert are O(1) on average. Without Map ordering, the classic answer is a hash map plus a doubly linked list.
  • How does structuredClone differ from a hand-written copy? It handles cycles, Date, Map, Set, typed arrays and more, but throws on functions and DOM nodes and drops the prototype of class instances.
  • What should memoize use as a key for several arguments? A serialized key (JSON.stringify(args)) for primitives, or nested Map / WeakMap per argument for objects; and an unbounded cache leaks memory, which is where an LRU limit comes in.
  • How do you cancel the tasks of runWithLimit after a failure? Pass an AbortSignal to each task and stop the workers when it is aborted; by itself Promise.all only stops waiting.

Common Mistakes​

  • Losing this and arguments in a wrapper: an arrow function as the returned function, or fn() instead of fn.apply(this, args).
  • A Promise.all that never settles for an empty array, or that pushes results in completion order instead of input order.
  • JSON.parse(JSON.stringify(obj)) as a deep copy without naming what it loses: Date, undefined, Map, Set, cycles.
  • Recursion without tracking visited objects: a cyclic object overflows the stack.
  • Mutating the input array or object in a utility that should return a new one.
  • Writing code silently: the interviewer checks the reasoning as much as the result.

See Also​