Functional Array Patterns
Practical functional array techniques — flatMap, partitioning, indexing, zip, and when chaining hurts performance.
- javascript
- arrays
- functional
- patterns
Beyond map/filter/reduce, a small set of patterns shows up constantly in UI data shaping. Knowing them by name makes code reviews shorter and interview whiteboards less blank.
flatMap for map + flatten
const users = [
{ name: 'Ada', tags: ['js', 'wasm'] },
{ name: 'Grace', tags: ['cobol'] },
];
users.flatMap((u) => u.tags);
// ['js', 'wasm', 'cobol']
// conditional emit zero or more
[1, 2, 3, 4].flatMap((n) => (n % 2 === 0 ? [n, n * 10] : []));
// [2, 20, 4, 40]
Prefer flatMap over map(...).flat().
Partition
function partition(arr, pred) {
return arr.reduce(
([pass, fail], x) => {
(pred(x) ? pass : fail).push(x);
return [pass, fail];
},
[[], []],
);
}
const [active, inactive] = partition(users, (u) => u.active);
One pass instead of two filters.
Index by key
function indexBy(arr, keyFn) {
return arr.reduce((map, item) => {
map.set(keyFn(item), item);
return map;
}, new Map());
}
const byId = indexBy(users, (u) => u.id);
byId.get(3);
Map keeps insertion order and any key type; plain objects coerce keys to strings.
Group by
function groupBy(arr, keyFn) {
return arr.reduce((acc, item) => {
const k = keyFn(item);
if (!acc.has(k)) acc.set(k, []);
acc.get(k).push(item);
return acc;
}, new Map());
}
Unique
const unique = [...new Set(ids)];
// unique by key
function uniqueBy(arr, keyFn) {
const seen = new Set();
return arr.filter((item) => {
const k = keyFn(item);
if (seen.has(k)) return false;
seen.add(k);
return true;
});
}
Zip and chunk
function zip(a, b) {
const n = Math.min(a.length, b.length);
return Array.from({ length: n }, (_, i) => [a[i], b[i]]);
}
function chunk(arr, size) {
const out = [];
for (let i = 0; i < arr.length; i += size) {
out.push(arr.slice(i, i + size));
}
return out;
}
chunk([1, 2, 3, 4, 5], 2); // [[1,2],[3,4],[5]]
Sort without mutate
const sorted = [...items].sort((a, b) => a.score - b.score);
// or items.toSorted(...) in modern engines
sort mutates — copy first (see sorting arrays correctly).
Chaining vs one pass
// readable, intermediate arrays
arr.filter(isActive).map(toDto).slice(0, 20);
// hot path
const out = [];
for (const x of arr) {
if (!isActive(x)) continue;
out.push(toDto(x));
if (out.length === 20) break;
}
Default to clarity; fuse loops when profiling says so.
Interview answer
“I use flatMap for nested lists, partition/groupBy/indexBy as reduce patterns, Set for uniqueness, and copy-before-sort. I chain map/filter for small UI data and collapse to one loop for large hot paths. Map beats object for non-string keys.”
Related
window sliding and pairs
function* windows(arr, size) {
for (let i = 0; i <= arr.length - size; i++) {
yield arr.slice(i, i + size);
}
}
function pairs(arr) {
return arr.slice(0, -1).map((x, i) => [x, arr[i + 1]]);
}
// sum of deltas
pairs(prices).map(([a, b]) => b - a);
These show up in charting and form wizards (“steps”). Keep helpers pure and generic so UI code reads as data flow. If a helper is used once, an inline loop is fine — extract when the name clarifies intent (partition, indexBy, uniqueBy).
Further reading
Related guides
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- Compose and PipeRight-to-left compose vs left-to-right pipe — building unary pipelines, debugging intermediate values, and when a plain function is enough.
- Destructuring Objects and ArraysObject and array destructuring — renames, defaults, nested patterns, rest, and parameter destructuring in real APIs.
- Event Emitter PatternImplement on/off/emit with Map of Sets — once, error isolation, memory leaks, and how this differs from DOM events.