JavaScript Stack Overflow Answers
Selected technical solutions from my Stack Overflow history. These cover iteration, Proxies, the event loop, and data transforms — not a vote leaderboard.
Problem
for...of on an array of objects was used as if it yielded keys and values. It threw because the loop was iterating values, not entries.
Solution
Use collection.entries() when you need index plus value. If you only need fields on each item, destructure: for (const { name, views } of collection).
Why it works
for...of walks the iterable's values. Array is iterable over elements, not [index, value] pairs. entries() is the iterator that yields both.
Code
for (const [k, v] of collection.entries()) {
console.log(k, v);
}
for (const { name, views } of collection) {
console.log(name, views);
}
8 score · Accepted✓
View on Stack Overflow ↗Problem
toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' }) produced Jun 09, 2023. The asker wanted Jun/09/2023 and thought the locale API could take a custom delimiter.
Solution
Reuse one Intl.DateTimeFormat if you must stay in Intl — constructing it per call is expensive. For lists, format the parts yourself. Manual formatting beat regexp and formatToParts in the benchmark.
Why it works
toLocaleDateString is a wrapper that builds Intl.DateTimeFormat every time. A delimiter is not a locale option; you join parts, rewrite the string, or skip Intl.
Code
const fmt = new Intl.DateTimeFormat('en-US', {
year: 'numeric', day: '2-digit', month: 'short'
});
fmt.formatToParts(date).filter(p => p.type !== 'literal').map(p => p.value).join('/');
7 score · Accepted · 2.4K question views✓
View on Stack Overflow ↗Problem
MutationObserver callbacks run as microtasks. The asker wanted to know why they are not macrotasks.
Solution
The callback must run before the browser paints at the end of the current macrotask. If it waited for another macrotask, DOM writes in the observer would paint a frame late (flicker). It also cannot be sync: every mutation would re-enter the observer.
Why it works
Microtasks drain after the script, before rendering. That is the window where the DOM is settled for this turn but the screen has not updated yet.
4 score · Accepted✓
View on Stack Overflow ↗Problem
console.log(array) before a push showed one item in the summary, but expanding the object in DevTools showed two. The live preview was mutating under the inspector.
Solution
Log a snapshot: structuredClone(array), or a shallow copy with array.slice() / [...array] if nested mutation does not matter.
Why it works
Chrome keeps a reference. Expanding later evaluates the object as it is now, not as it was at log time. A clone freezes that moment.
Code
console.log(structuredClone(array));
array.push({ fruit: 'apple' });
console.log(structuredClone(array));
5 score · Accepted · 1.7K question views✓
View on Stack Overflow ↗Problem
foodsList repeated originCode / foodOrigin. The unique pairs needed to be collected fast.
Solution
Walk with a C-style for loop. Map.has(originCode) || Map.set(...). Then Array.from(map, ([originCode, foodOrigin]) => ({ foodOrigin, originCode })). The has() check is what makes the Map path win.
Why it works
The result is tiny; iterating the source is the cost. for (let i) plus a Map lookup beats filter/reduce on large inputs.
Code
const map = new Map();
for (let i = 0; i < foodsList.length; i++) {
const item = foodsList[i];
map.has(item.originCode) || map.set(item.originCode, item.foodOrigin);
}
const result = Array.from(map, ([originCode, foodOrigin]) => ({ foodOrigin, originCode }));
6 score · Accepted · 232 question views✓
View on Stack Overflow ↗Problem
Objects { name, id, class } needed to become rows ordered by a header array th, at 10k+ rows, without relying on forEach/map for speed.
Solution
map is fine for clarity: arr.map(row => th.map(name => row[name])). The faster path preallocates and uses nested for loops.
Why it works
The inner index into th is a property lookup, not Object.values order. Preallocated arrays skip grow-and-copy.
Code
const result = Array(arr.length);
for (let i = 0; i < arr.length; i++) {
const item = result[i] = Array(th.length);
for (let j = 0; j < th.length; j++) {
item[j] = arr[i][th[j]];
}
}
6 score · Accepted · 308 question views✓
View on Stack Overflow ↗Problem
A subclass could replace a base getter with a field, but a base field could not be replaced by a subclass getter.
Solution
Getters live on the prototype; fields are own properties. Delete the inherited own field in the subclass constructor if a prototype getter should win.
Why it works
Own properties shadow the prototype. The instance field is found first, so the getter on B.prototype is never consulted until this.foo is deleted.
Code
class B extends A {
constructor() {
super();
delete this.foo;
}
get foo() {
return 'baz';
}
}
6 score · Accepted✓
View on Stack Overflow ↗Problem
A flat list with lv (depth) described a tree relative to previous nodes. Recursion broke when a shallower node followed a deeper one.
Solution
Keep last[lv] as the current parent at that depth. Push each node onto last[lv].children and set last[lv + 1] = node. No stack, no recursion.
Why it works
Going back up just writes into an earlier last[lv]. The previous deep nodes are already attached and can be ignored.
Code
const result = [], last = [{ children: result }];
for (const { lv, name } of arr) {
const node = { name, children: null };
(last[lv].children ??= []).push(node);
last[lv + 1] = node;
}
4 score · Accepted · 358 question views✓
View on Stack Overflow ↗Problem
A has trap made 'secret' in proxy false, but proxy.secret still returned 123.
Solution
Coordinate get, has, ownKeys, and getOwnPropertyDescriptor. A hideKeys helper returns undefined from get and filters ownKeys.
Why it works
in only uses has. Property access uses get. Object.keys uses ownKeys plus getOwnPropertyDescriptor. One trap is not a hide.
Code
get(t, prop) {
if (keys.has(prop)) return;
return Reflect.get(...arguments);
},
has(t, prop) {
if (keys.has(prop)) return false;
return Reflect.has(...arguments);
}
4 score
View on Stack Overflow ↗Problem
x << 1 vs x * 2 (and >> vs /) for “the fastest” doubling/halving.
Solution
On Chrome they measured the same. Write * 2 / 2 unless you mean bits.
Why it works
JITs lower both to the same arithmetic. Micro-optimizing the operator does not beat the optimizer.
Code
// @benchmark shift
2 << 1
// @benchmark multiply
2 * 2
3 score · Accepted · 167 question views✓
View on Stack Overflow ↗Problem
Same calendar day, different hours. Splitting toISOString() on T was too slow in a tight loop.
Solution
Drop the time with integer math: (t - t % DAY_TIME). Subtract timezone offset first if you need local dates.
Why it works
getTime() is a number. Modulo a day in ms clears the clock without allocating strings.
Code
const DAY_TIME = 3600 * 24 * 1000;
const isSameDate = (a, b) => {
a = a.getTime();
b = b.getTime();
return (a - a % DAY_TIME) === (b - b % DAY_TIME);
};
3 score · Accepted · 138 question views✓
View on Stack Overflow ↗Problem
Recursive readdirSync plus statSync per name was slow on a large tree. Async was slower.
Solution
readdirSync(dir, { withFileTypes: true }) and use Dirent.isDirectory() so you skip a second stat. Node 20 recursive:true was slower in that test.
Why it works
withFileTypes fills type from the directory listing. A follow-up stat doubles syscalls.
Code
const fileList = fs.readdirSync(dir, { withFileTypes: true });
fileList.forEach(file => file.isDirectory()
? getFiles(`${dir}/${file.name}`, files)
: files.push(`${dir}/${file.name}`));
3 score · Accepted✓
View on Stack Overflow ↗Problem
Assigning Function.prototype = new Proxy(...) was ignored. toString hooks never ran.
Solution
You cannot replace Function.prototype. Copy descriptors onto a mask object, proxy that, delete the own keys on Function.prototype, then Object.setPrototypeOf(Function.prototype, proxy).
Why it works
Function.prototype is a non-writable built-in. Its [[Prototype]] can still be swapped, so lookups go through the proxy.
Code
Object.setPrototypeOf(Function.prototype, prototype);
2 score · Accepted · 548 question views✓
View on Stack Overflow ↗Problem
Pairs like ["DL"], ["DL","ATL"] needed grouping under the parent code, keeping parent order, including when a child appeared before its parent.
Solution
reduce into a map of parent → children, then Object.entries and push [key], ...val. sort() can encode the same rule but was much slower at 500× size.
Why it works
The map records first-seen parent order. Flattening entries reconstructs parent then children without a full sort.
Code
const mapped = arr.reduce((map, item) => {
map[item[0]] ??= [];
item.length > 1 && map[item[0]].push(item);
return map;
}, {});
const result = Object.entries(mapped).reduce((result, [key, val]) => {
result.push([key], ...val);
return result;
}, []);
2 score · Accepted✓
View on Stack Overflow ↗Problem
for...in on a string yielded extra keys like d, defaultMessage, format — not just character indexes.
Solution
Something had put enumerable properties on String.prototype (or Object.prototype). Use for...of, which walks the string iterator (characters).
Why it works
for...in enumerates enumerable keys including the prototype chain. Strings are exotic objects; you still inherit prototype enumerables. for...of uses Symbol.iterator.
Code
for (const ch of str) {
console.log(ch);
}
2 score · Accepted✓
View on Stack Overflow ↗Problem
A nested CPU loop never let the progress element paint. Updates sat until the script finished.
Solution
Move the work into a Worker (blob URL from the function source). postMessage progress; the main thread assigns progress.value. Promise resolves with duration.
Why it works
The main thread must return to the event loop to paint. A worker is a second thread, so messages can update the DOM while the loop runs.
Code
const worker = new Worker(URL.createObjectURL(blob));
worker.addEventListener('message', e => {
if ('duration' in e.data) resolve(e.data.duration);
else progressCb(e.data.progress);
});
2 score · Accepted✓
View on Stack Overflow ↗Problem
Each object in an array needed values overwritten from a Map when the Map key existed on the object.
Solution
Cache [...map2.keys()] once. Then for each item, walk those keys: (key in item) && (item[key] = map2.get(key)).
Why it works
The Map is the smaller set. Caching keys avoids repeating Map iteration per row.
Code
const keys = [...map2.keys()];
arr1.forEach(item => keys.forEach(key => (key in item) && (item[key] = map2.get(key))));
2 score
View on Stack Overflow ↗Problem
Passing structuredClone as a method (obj.clone = structuredClone) threw Illegal invocation in Chrome/Edge. Node was fine.
Solution
Browsers require the Window this. Call with .call(window, value) or bind: structuredClone.bind(null).
Why it works
There can be several Window objects (iframes). The platform function checks this. Node has one global, so the check is skipped.
Code
({ clone: structuredClone.bind(null) }).clone(1);
1 score · Accepted✓
View on Stack Overflow ↗Problem
The object needed both properties and () invocation.
Solution
A function is already an object — attach props, but watch name/length. Or Proxy a Function: apply handles calls, get/set forward to a store object.
Why it works
Only callables have [[Call]]. A Proxy around new Function intercepts apply while looking like a function to the engine.
Code
const callableObject = new Proxy(new Function, {
apply() { console.log('invoked'); },
get(_, prop) { return Reflect.get(obj, prop); },
set(_, prop, val) { Reflect.set(obj, prop, val); }
});
1 score · Accepted✓
View on Stack Overflow ↗Problem
Watching an attribute: Proxy/decorator vs MutationObserver.
Solution
Use MutationObserver for DOM. It sees setAttribute, the Attr node, and subtree adds. A Proxy only sees mutations that go through that proxy.
Why it works
Attributes change through many APIs. Observer listens to the actual DOM, not one wrapper.
Code
new MutationObserver(list => {
console.log('aria-hidden changed');
}).observe($test, { attributeFilter: ['aria-hidden'] });
1 score · Accepted✓
View on Stack Overflow ↗ View All Answers ↗