Chunk an Array into Fixed Sizes
Splitting an array into fixed-size groups is a recurring need for pagination, batch API calls, and grid layouts. This snippet covers a one-line slice loop, a generator variant for streaming large inputs, and the edge cases (size <= 0, non-integer size, non-multiple lengths) that bite production code. Drop it in as a tiny utility and stop reaching for lodash for one helper.
544 views
17
function chunk(array, size) {
const result = [];
for (let i = 0; i < array.length; i += size) {
result.push(array.slice(i, i + size));
}
return result;
}
console.log(chunk([1, 2, 3, 4, 5], 2));
// [[1, 2], [3, 4], [5]]The simplest correct implementation walks the source in steps of size and pushes a slice(i, i + size) for each window. slice clamps to array.length, so the final group is whatever is left, which is why [1, 2, 3, 4, 5] chunked by 2 yields a trailing [5]. This runs in O(n) time and O(n) extra space because every element is copied exactly once into a new array. Reach for this version when the input fits comfortably in memory and you just need a clean pagination helper.
function chunk(array, size) {
if (!Array.isArray(array)) throw new TypeError('chunk: array must be an Array');
if (!Number.isInteger(size) || size <= 0) {
throw new RangeError('chunk: size must be a positive integer');
}
const result = [];
for (let i = 0; i < array.length; i += size) {
result.push(array.slice(i, i + size));
}
return result;
}
console.log(chunk([], 3)); // []
console.log(chunk(['a'], 5)); // [['a']]
try { chunk([1, 2], 0); } catch (e) { console.log(e.message); }
// chunk: size must be a positive integerThe minimal version silently misbehaves on bad input: size = 0 causes an infinite loop and size = 1.5 produces overlapping groups. Guarding Number.isInteger(size) && size > 0 up front turns a hang into a clear error. The empty-array case naturally returns [] without special handling because the loop simply never runs, and a size larger than the input still works (the single slice covers everything). Use this hardened variant whenever the size comes from user input or a config value you do not control.
function* chunkLazy(iterable, size) {
let buffer = [];
for (const item of iterable) {
buffer.push(item);
if (buffer.length === size) {
yield buffer;
buffer = [];
}
}
if (buffer.length > 0) yield buffer;
}
for (const group of chunkLazy([1, 2, 3, 4, 5, 6, 7], 3)) {
console.log(group);
}
// [1, 2, 3]
// [4, 5, 6]
// [7]When the source is a stream, an async iterator, or an array with millions of entries, materialising every chunk up front wastes memory. A generator yields each group as it fills, so the consumer can process and discard it before the next one is built. This works on any iterable, not just arrays, which is handy for fetch response bodies, generators, or Set instances. The trailing if (buffer.length > 0) flush is the easy line to forget; without it, partial final groups vanish.
