# Grouping array elements into batches of at most three

const items = ['a', 'b', 'c', 'd']

const reduced = items.reduce((acc, cur, index) => {
const arrayIndex = Math.ceil((index + 1) / 3) - 1

if (acc[arrayIndex]) {
acc[arrayIndex].push(cur)
} else {
acc.push([cur])
}

return acc
}, [])


I'm taking an array of items, batching them into arrays of three at most and returning them as arrays of array. Here reduced yields the correct structure of [["a", "b", "c"], ["d"]]. How do I accomplish this without the if statement (which mutates the accumulated value directly)?

• I think this question is off topic for code review (you are asking "How to?" not "Review my code?") but while here just copy the array each pass a = [...a] eg var b='abcd'.split("").reduce((a,v,i)=>((a=[...a][i/3|0]=i%3?a[i/3|0]:[])[i%3]=v,a),[]); Sorry a little squished to fit the comment. – Blindman67 Oct 15 '17 at 3:50

If you want to do it without modifying the arrays in accumulator whatsoever you can use the slice method.

const items = ['a', 'b', 'c', 'd']

var acc = [];
const reduced = items.reduce((acc, cur, index) => {
const arrayIndex = index % 3;

if (arrayIndex != 0) {
return [...acc.slice(0,-1), [...acc.slice(-1),cur]];
} else {
return [...acc, [cur]]
}
}, acc)

console.log(reduced);
console.log(acc);

If your list is as simple as it is with single values you can likely just slice before you accumulate in your method this will create a copy of the array so that you can mutate the copy this is likely all you really need.

const items = ['a', 'b', 'c', 'd']

var acc = [];
const reduced = items.reduce((acc, cur, index) => {
const arrayIndex = Math.ceil((index + 1) / 3) - 1

if (acc[arrayIndex]) {
acc[arrayIndex].push(cur)
} else {
acc.push([cur])
}

return acc
}, acc.slice());

console.log(reduced);
console.log(acc);

For reference here is your function with the mutations.

const items = ['a', 'b', 'c', 'd']

var acc = [];
const reduced = items.reduce((acc, cur, index) => {
const arrayIndex = Math.ceil((index + 1) / 3) - 1

if (acc[arrayIndex]) {
acc[arrayIndex].push(cur)
} else {
acc.push([cur])
}

return acc
}, acc);

console.log(reduced);
console.log(acc);

• If my assumption that your question was about not making it mutate was incorrect let me know. – John Oct 15 '17 at 3:06
• Thanks for showing the array spread operator in action. I've awarded this the chosen answer as you did address my explicit question. The broader question I had was, is mutating the accumulator value not recommended and why? – Kunal Oct 15 '17 at 23:30
• @Kunal mutating the accumulator during the iteration is fine as long as you don't need the value passed in to the accumulator to remain unmutated. Generally you just create a new array so it is fine if you mutate it. If you were to start your reduce with an array that's passed in and is prefilled then you may want to make sure you don't mutate it directly. The slice (2nd) method would work fine in that hypothetical situation. Basically just make sure you're not mutating state of an object/array out from under any of its users. – John Oct 16 '17 at 0:12

If you are not beholden to using reduce, you could chunk your array like this.

let chunked = [];
for (let i = 0; i < items.length; i = i + 3) {
chunked.push(items.slice(i, i + 3));
}


Just for kicks, I implemented a jsFiddle to understand performance of Array.reduce() (as in original post) compared to for loop I have suggested and was somewhat surprised to find the for loop to be on the order of 50% slower than reduce.

When testing for larger arrays (100K items for example) performance became similar between the two solutions.

Regardless, it is probably very much in the territory of micro-optimization to consider one approach vs. the other based on performance testing alone. I would still prefer the simpler for loop code from an code management standpoint unless I knew I was going to be running this code at very high frequency in my application. However if the majority of the surrounding application was using more of a functional programming style, I would be happy to use reduce as well.