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 ↗