Rebuild the tools you use
Implementing built-ins proves you understand closures, this, promises and edge cases. Talk through the contract (inputs, outputs, errors) before you write code.
- Polyfillsmap, bind and Promise.all.
- UtilitiesThrottle, memoize, curry and deep clone.
- PatternsAn event emitter with unsubscribe.
Front-end machine-coding rounds love “implement it yourself” questions: write bind, build Promise.all, create an event emitter. They check that you understand the language deeply — closures, this, promises and prototypes — not just how to call APIs.
1. Write a polyfill for Array.prototype.map.
Respect the contract: call the callback with (element, index, array), honour thisArg, skip holes in sparse arrays and return a new array.
if (!Array.prototype.myMap) {
Array.prototype.myMap = function (callback, thisArg) {
if (typeof callback !== 'function') throw new TypeError(callback + ' is not a function');
const result = new Array(this.length);
for (let i = 0; i < this.length; i++) {
if (i in this) result[i] = callback.call(thisArg, this[i], i, this);
}
return result;
};
}
[1, 2, 3].myMap(x => x * 2); // [2, 4, 6]2. Implement Function.prototype.bind.
Return a function that calls the original with a fixed this and pre-filled arguments, appending later arguments.
Function.prototype.myBind = function (context, ...preset) {
const fn = this;
return function (...later) {
return fn.apply(context, [...preset, ...later]);
};
};
const user = { name: 'Asha' };
function greet(greeting, mark) { return `${greeting}, ${this.name}${mark}`; }
greet.myBind(user, 'Hello')('!'); // 'Hello, Asha!'3. Implement Promise.all.
Resolve with results in the original order once all promises resolve; reject as soon as one rejects. Handle non-promise values and an empty input.
function promiseAll(items) {
return new Promise((resolve, reject) => {
const results = [];
let remaining = items.length;
if (remaining === 0) return resolve(results);
items.forEach((item, i) => {
Promise.resolve(item).then(value => {
results[i] = value;
if (--remaining === 0) resolve(results);
}, reject);
});
});
}4. Write a throttle function.
Throttle lets a function run at most once per interval — for scroll and resize handlers. This version also runs the last call at the end of the interval so the final state is not lost.
function throttle(fn, wait) {
let last = 0, timer = null, lastArgs;
return function (...args) {
const now = Date.now();
lastArgs = args;
if (now - last >= wait) {
last = now;
fn.apply(this, args);
} else if (!timer) {
timer = setTimeout(() => {
last = Date.now();
timer = null;
fn.apply(this, lastArgs);
}, wait - (now - last));
}
};
}5. Write a memoize function.
Cache results by arguments in a Map. Use a key that represents all arguments; for object arguments a WeakMap or a custom key function avoids leaks and collisions.
function memoize(fn, key = (...args) => JSON.stringify(args)) {
const cache = new Map();
return function (...args) {
const k = key(...args);
if (!cache.has(k)) cache.set(k, fn.apply(this, args));
return cache.get(k);
};
}
const slowSquare = n => { for (let i = 0; i < 1e7; i++); return n * n; };
const fastSquare = memoize(slowSquare);6. Implement curry.
A curried function collects arguments until it has as many as the original function expects, then calls it.
function curry(fn) {
return function curried(...args) {
return args.length >= fn.length
? fn.apply(this, args)
: (...more) => curried.apply(this, [...args, ...more]);
};
}
const add3 = curry((a, b, c) => a + b + c);
add3(1)(2)(3); // 6
add3(1, 2)(3); // 67. Deep clone an object without structuredClone.
Recursively copy arrays and plain objects, keep dates and handle circular references with a WeakMap.
function deepClone(value, seen = new WeakMap()) {
if (value === null || typeof value !== 'object') return value;
if (value instanceof Date) return new Date(value);
if (seen.has(value)) return seen.get(value); // circular reference
const copy = Array.isArray(value) ? [] : {};
seen.set(value, copy);
for (const key of Object.keys(value)) copy[key] = deepClone(value[key], seen);
return copy;
}8. Build a simple event emitter.
Store listeners per event name; on returns an unsubscribe function, once removes itself after the first call, and emit calls a copy of the listener list so listeners can unsubscribe safely while emitting.
class EventEmitter {
#events = new Map();
on(name, fn) {
if (!this.#events.has(name)) this.#events.set(name, new Set());
this.#events.get(name).add(fn);
return () => this.#events.get(name)?.delete(fn);
}
once(name, fn) {
const off = this.on(name, (...args) => { off(); fn(...args); });
return off;
}
emit(name, ...args) {
for (const fn of [...(this.#events.get(name) ?? [])]) fn(...args);
}
}Remember
Key points
- State the contract first
Arguments, return value and edge cases.
- Keep result order
Promise.all returns results in input order.
- Use apply/call for this
Preserve the caller's context.
- Handle cycles
WeakMap in recursive copies.
Interview drill
Follow-up questions
What the interviewer asks after your first answer. Turn on practice mode, answer each one aloud, then reveal.
-
1 Implement Promise.allSettled. Intermediate
Map each item to a promise that always resolves with a status object, then use Promise.all.
const allSettled = items => Promise.all(items.map(p => Promise.resolve(p).then( value => ({ status: 'fulfilled', value }), reason => ({ status: 'rejected', reason }), ))); -
2 Flatten a nested array to any depth. Basic
arr.flat(Infinity), or recursively with reduce and concat. -
3 Write a function that retries a promise-returning function. Intermediate
Loop with try/catch, wait with exponential back-off between attempts, rethrow after the last failure.
async function retry(fn, times = 3, delay = 500) { for (let i = 1; ; i++) { try { return await fn(); } catch (e) { if (i >= times) throw e; await new Promise(r => setTimeout(r, delay * 2 ** (i - 1))); } } } -
4 Implement a sleep function. Basic
const sleep = ms => new Promise(r => setTimeout(r, ms));thenawait sleep(1000). -
5 Write a function to get a nested value safely, like get(obj, "a.b.c"). Basic
Split the path and reduce with optional chaining.
const get = (obj, path, fallback) => path.split('.').reduce((acc, key) => acc?.[key], obj) ?? fallback; -
6 How would you implement a simple LRU cache in JavaScript? Advanced
A Map keeps insertion order: on get, delete and re-set the key to move it to the end; on set beyond capacity, delete
map.keys().next().value(the oldest).