DEV Community

Cover image for How to split arrays into equal-sized chunks
Dom Habersack
Dom Habersack

Posted on • Originally published at islovely.co

How to split arrays into equal-sized chunks

JavaScript provides a way to split strings into arrays with split(). If we want to split arrays into smaller arrays, we have to do so by hand, as there is no native function for that. To break a long list of elements into smaller groups, we can use a combination of map() and slice().

Let’s say we have a lot of ducks. In the beginning, we have all our ducks in a row single array:

['duck1', 'duck2', 'duck3', 'duck4', 'duck5', 'duck6', 'duck7', 'duck8', 'duck9', 'duck10', 'duck11', 'duck12']
Enter fullscreen mode Exit fullscreen mode

We want to neatly organize our ducks. Because they don’t all fit on a single shelf, we want to put them on several smaller shelves. We know that each shelf holds four ducks, so we want to group them like this:

[
  ['duck 1', 'duck 2',  'duck 3',  'duck 4'],
  ['duck 5', 'duck 6',  'duck 7',  'duck 8'],
  ['duck 9', 'duck 10', 'duck 11', 'duck 12']
]
Enter fullscreen mode Exit fullscreen mode

Instead of containing ducks directly, this array contains three smaller arrays. Each of these arrays then contains a set of four ducks. We can write a function to build this structure for us:

const chunkArray = (array, chunkSize) => {
  const numberOfChunks = Math.ceil(array.length / chunkSize)

  return [...Array(numberOfChunks)]
    .map((value, index) => {
      return array.slice(index * chunkSize, (index + 1) * chunkSize)
    })
}
Enter fullscreen mode Exit fullscreen mode

This function takes an array and chunk size and returns it grouped into chunks of that size. If we cannot split the values evenly, the last chunk will contain fewer elements:

chunkArray(['a', 'b', 'c', 'd'], 2)
// => [
//      ['a', 'b'],
//      ['c', 'd']
//    ]

chunkArray([1, 2, 3, 4, 5, 6], 3)
// => [
//      [1, 2, 3],
//      [4, 5, 6]
//    ]

chunkArray([true, true, false, true, false, false, true], 4)
// => [
//      [true, true, false, true],
//      [false, false, true]
//    ]
Enter fullscreen mode Exit fullscreen mode

Let’s look at how this works line by line:

const chunkArray = (array, chunkSize) => {
Enter fullscreen mode Exit fullscreen mode

The function chunkArray takes an array and the desired size of each chunk in its parameters.

const numberOfChunks = Math.ceil(array.length / chunkSize)
Enter fullscreen mode Exit fullscreen mode

We need to know how many groups, or chunks, we need if we want to split the array into sets of the desired size. We get that value by dividing the number of elements in the array by the number of elements we want to have in each chunk. Four or eight ducks fit into four-element chunks nicely. To split six ducks into groups of four, we would need 1.5 chunks, because 6 divided by 4 is 1.5.

Each chunk is an array. Because there are no half arrays, we round the result to the next-largest integer with Math.ceil(). For our six ducks, we need to use two chunks to split them in groups of four. The second chunk will be half empty, which is okay.

On to the next line.

return [...Array(numberOfChunks)]
Enter fullscreen mode Exit fullscreen mode

Now that we know how many chunks we need, we create an outer array with this many empty spaces. Array(length) returns an array that has its length set to the value we pass to it. That array is empty. It does not even contain undefined values:

Array(3)
// => []

Array(3).length
// => 3
Enter fullscreen mode Exit fullscreen mode

We want to iterate over these spaces with map() in the next step. Because we cannot iterate over an empty array, we need to put values into those empty spaces. We initialize a new array from the one we already created using the spread syntax. This way, the new array has the same length as the previous one, with each value set to undefined:

[...Array(3)]
// => [undefined, undefined, undefined]
Enter fullscreen mode Exit fullscreen mode

We can now iterate over this array with .map():

.map((value, index) => {
Enter fullscreen mode Exit fullscreen mode

The value will be undefined in each iteration. We don’t care much about the value, but we will use the index. If we split the array into three groups, the index goes from 0 to 2. We will use that to grab shorter sections out of the array next.

return array.slice(index * chunkSize, (index + 1) * chunkSize))
Enter fullscreen mode Exit fullscreen mode

slice() returns a shallow copy of the array we call it on. Both parameters are index values that refer to positions in the array. When extracting a partial copy, slice() starts at the first value and stops before the second value. If the second value is greater than the length of the array, it stops at the end of the array:

['mouse', 'hamster', 'rabbit', 'fox', 'koala'].slice(0, 2)
// => ['mouse', 'hamster']

['mouse', 'hamster', 'rabbit', 'fox', 'koala'].slice(2, 4)
// => ['rabbit', 'fox']

['mouse', 'hamster', 'rabbit', 'fox', 'koala'].slice(4, 6)
// => ['koala']
Enter fullscreen mode Exit fullscreen mode

We use each chunk’s index to calculate the parameters for slice(). By multiplying it by the size of each chunk, we copy groups of that many values from the array. If our chunkSize is 4, these are the slices we would extract:

// index = 0
array.slice(0, 4)

// index = 1
array.slice(4, 8)

// index = 2
array.slice(8, 12)
Enter fullscreen mode Exit fullscreen mode

map() returns a new array. Instead of several undefined values, our function returns slices of the original array. Each of these slices is one chunk that contains four items. The outcome looks exactly like what we wanted:

chunkArray(['duck1', 'duck2', 'duck3', 'duck4', 'duck5', 'duck6', 'duck7', 'duck8', 'duck9', 'duck10', 'duck11', 'duck12'], 4)
// => [
//      ['duck 1', 'duck 2',  'duck 3',  'duck 4'],
//      ['duck 5', 'duck 6',  'duck 7',  'duck 8'],
//      ['duck 9', 'duck 10', 'duck 11', 'duck 12']
//    ]
Enter fullscreen mode Exit fullscreen mode

What would I use this for?

Why would we want to chunk arrays into smaller groups in the first place? There are more realistic use cases than organizing ducks on shelves. Instead of strings or other primitive types, our array could contain more complex elements.

The array could hold posts we want to show on a news feed. To inject an ad slot after every tenth post, we could use a counter that keeps track of the posts while we show them. Every time that counter is divisible by ten, we could inject an ad before continuing with the next post. Keeping track of that counter is messy and likely to lead to errors.

If we split the posts into chunks of ten instead, we don’t need this counter anymore. We can take the long list of posts, split it into smaller groups, and place an ad between each of the groups.

The array could also hold product reviews instead of ducks or posts on a news feed. To not overwhelm users with all reviews at once, we can show them in batches. We could show five reviews at first, and then reveal the next five with every use of a “show more”-action.

Whenever we want to inject something in an array at regular intervals, we can chunk the array first.

Top comments (5)

Collapse
 
marcobiedermann profile image
Marco Biedermann

Great post! Very detailed.
Instead of using the spread operator, you could also make use of Array.from. You can define the length by passing in an object and use the second argument to loop over it. No need to call Array.map on it

const chunkArray = (array, size: number) => {
  const numberOfChunks = Math.ceil(array.length / chunkSize)

  return Array.from(
    {
      length: numberOfChunks,
    },
    (_, index) => array.slice(index * chunkSize, (index + 1) * chunkSize)
  )
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
domhabersack profile image
Dom Habersack

This is fantastic, thanks so much for sharing!

Collapse
 
codefinity profile image
Manav Misra

🔥 Hot tip. Welcome to the community!

Collapse
 
alimobasheri profile image
MirAli Mobasheri

Thanks for sharing, Dom. It was an interesting read.
I was curious to achieve the same results using Array.reduce.

This is the solution I came up with:

const chunkArray = (array, chunkSize) => 
    array.reduce((chunks, value, index) => {
        return index % chunkSize === 0 ?
            [...chunks, [value]] :
            chunks.map((chunk, idx) => 
                idx === chunks.length-1 ?
                    chunk.concat(value) :
                    chunk
            )
    }, [])
Enter fullscreen mode Exit fullscreen mode
Collapse
 
domhabersack profile image
Dom Habersack

Awesome! I hadn’t even considered using reduce for this. 👏