Object.groupBy & groupBy()
intermediateImplement groupBy from scratch, then meet the ES2024 built-ins — including the null-prototype result and Map.groupBy for object keys.
The modern JavaScript features interviewers now expect — Object.groupBy, Promise.withResolvers, Set methods, iterator helpers with lazy evaluation, RegExp.escape, and how to use them under interview pressure.
Short answer: Senior interviews increasingly close an implementation question with a twist — "does the language give you this for free now?" The modern built-ins worth knowing cold: Object.groupBy/Map.groupBy (grouping without a hand-rolled reduce), Promise.withResolvers (the standardized deferred pattern), Set methods (intersection/union/difference for O(n) set algebra), and iterator helpers (map/filter/take directly on iterators, evaluated lazily). Knowing each and its limits signals that you keep current.
The pattern is consistent: you implement something by hand, and the interviewer asks whether the language now ships it. Reaching for Object.groupBy instead of a hand-rolled reduce, or Promise.withResolvers instead of the deferred pattern, shows you track the platform — and knowing each feature's limits is what separates recognition from fluency. This page is the shortlist that actually comes up, and how to deploy each under pressure.
Grouping — previously the most-written reduce in existence — is now built in:
const orders = [
{ id: 1, status: "paid" },
{ id: 2, status: "pending" },
{ id: 3, status: "paid" },
];
const byStatus = Object.groupBy(orders, (o) => o.status);
// { paid: [{id:1},{id:3}], pending: [{id:2}] }
const map = Map.groupBy(orders, (o) => o.status); // Map for arbitrary keysInterview notes: the result of Object.groupBy has a null prototype (no inherited keys — see prototypal inheritance), and Map.groupBy is the right choice when group keys are objects.
Creates a promise plus its resolve/reject functions in one call — the standardized "deferred" pattern. Full treatment in the Promises guide.
const { promise, resolve, reject } = Promise.withResolvers();A stricter, more powerful Unicode mode enabling set operations in character classes:
/[\p{Script=Latin}&&\p{Uppercase}]/v.test("A"); // intersection: uppercase LatinYou won't be asked to write these, but recognizing && (intersection) and -- (difference) inside [...] shows currency.
String.prototype.isWellFormed() and toWellFormed() detect/repair lone surrogates — relevant when an interviewer probes string edge cases (pairs nicely with the string-polyfill follow-ups in the Polyfills guide).
Await a change to shared memory without blocking the thread — niche, but name-droppable when worker communication comes up.
Until ES2025, map/filter/reduce lived only on Array.prototype. To transform an iterator — a generator, a Map's entries, a stream of ids — you spread it into an array first ([...iter].map(...), materializing everything) or hand-wrote generator plumbing. Iterator helpers put the familiar methods directly on iterators — map, filter, take, drop, flatMap, reduce, toArray, forEach, some, every, find, plus Iterator.from() to wrap any iterable — evaluated lazily:
function* naturals() { let n = 1; while (true) yield n++; }
const firstFiveEvenSquares = naturals() // infinite source — no problem
.filter((n) => n % 2 === 0)
.map((n) => n * n)
.take(5)
.toArray(); // [4, 16, 36, 64, 100] — no infinite array materializedThe interview point is lazy vs eager. Array chaining is eager — each step builds a full intermediate array:
huge.map(f).filter(g).slice(0, 10);
// walks all of huge twice, allocates two intermediate arraysIterator chaining flows one element through the whole pipeline at a time, and take(10) stops the source after ten survivors:
Iterator.from(huge).map(f).filter(g).take(10).toArray();
// touches only as many elements as needed to produce 10 resultsSo "how would you get the first 100 matches from a 10-million-row dataset?" is O(matches) work and constant memory, not O(n) allocations. Helpers work on anything iterable — map.entries().filter(…).map(…).toArray() with no intermediate spread.
The implementation question ("polyfill take and map for iterators") is a generator exercise:
function* mapIter(iter, fn) {
for (const value of iter) yield fn(value);
}
function* takeIter(iter, limit) {
if (limit <= 0) return;
let count = 0;
for (const value of iter) {
yield value;
if (++count >= limit) return; // early return also runs the source's finally (cleanup)
}
}
[...takeIter(mapIter(naturals(), (n) => n * 2), 3)]; // [2, 4, 6]Two things to say while writing it: for…of calls next() and, on early exit, the iterator's return() (how generators run finally blocks for resource cleanup); and these are iterator helpers, not iterable helpers — the result is single-pass, so call the source again for a fresh run. Async counterparts (AsyncIterator.prototype.map) are on their own track; today the interview-safe way to transform an async stream is an async generator (for await … yield), which bridges to cancellation and batching.
Real set algebra, finally:
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
a.intersection(b); // Set {2, 3}
a.union(b); // Set {1, 2, 3, 4}
a.difference(b); // Set {1}
a.symmetricDifference(b); // Set {1, 4}
a.isSubsetOf(b); // false
a.isDisjointFrom(new Set([9])); // trueIf an interviewer asks you to dedupe-and-compare arrays, converting to Set and using these beats nested loops — and stating the O(n) vs O(n·m) difference earns the point.
Safely embed user input in a dynamic regex — the standard answer to a long-standing footgun:
const re = new RegExp(RegExp.escape(userInput), "i");import config from "./config.json" with { type: "json" };Worth knowing as the standardized form of a thing bundlers faked for years.
Math.f16round, Float16Array — mention-only, relevant to graphics/ML-adjacent roles.
Object.groupBy" is the strongest possible framing.structuredClone doesn't clone functions (details here); iterator helpers don't exist on plain arrays' values in old runtimes; Object.groupBy returns a null-prototype object.Implement groupBy from scratch, then meet the ES2024 built-ins — including the null-prototype result and Map.groupBy for object keys.
The prototype chain, __proto__ vs prototype, and what class syntax actually does under the hood.
Implement debounce from scratch, with leading/trailing options and the classic search-input use case.
The four binding rules, arrow-function behavior, and the lost-this bugs that call, apply, and bind exist to fix.