Implementing Array Partitioning: A Deep Dive into the Lodash chunk Function
The chunk function is a utility designed to split a single array into multiple sub-arrays of a specific length. If the total number of elements isn't perfectly divisible by the chunk size, the final sub-array contains the remaining elements. This pattern is particularly useful in frontend development for batching expensive operations, such as DOM updates, to prevent the main thread from freezing.
Practical Application: Batch Processing
When dealing with thousands of data entries that need to be rendered or processed, doing it all at once can lead to a sluggish user interface. By partitioning the data, we can process it in chunks using requestAnimationFrame or setTimeout.
const rawData = new Array(10000).fill(0).map((_, i) => `Item ${i}`);
const batchedData = chunk(rawData, 200);
function renderChunks(batches) {
if (batches.length === 0) return;
const currentBatch = batches.shift();
const fragment = document.createDocumentFragment();
currentBatch.forEach(text => {
const el = document.createElement('p');
el.textContent = text;
fragment.appendChild(el);
});
document.body.appendChild(fragment);
// Schedule next batch
setTimeout(() => renderChunks(batches), 0);
}
renderChunks(batchedData);
Core Logic and Algorithm
The logic behind chunk involves two primary steps: determining the size of the resulting array and slicing the original array at specific intervals.
- Calculation: To find out how many sub-arrays are needed, divide the total length by the desired chunk size. Since a partial remainder still requires its own sub-array, the result is rounded up using
Math.ceil. - Extraction: Using a loop, the algorithm "slices" the original array from a starting index to an offset (index + size) and pushes that slice into the result container.
Source Code Implementation
Below is a refined implementation of the chunking logic. It ensures that inputs are valid and uses a clean loop to populate the new array.
/**
* Creates an array of elements split into groups the length of `size`.
* If `array` can't be split evenly, the final chunk will be the remaining elements.
*
* @param {Array} collection The array to process.
* @param {number} [size=1] The length of each chunk.
* @returns {Array} Returns the new array of chunks.
*/
function chunk(collection, size = 1) {
// Ensure size is a positive integer
const cleanSize = Math.max(size, 0);
const totalLength = collection == null ? 0 : collection.length;
if (!totalLength || cleanSize < 1) {
return [];
}
let cursor = 0;
let targetIndex = 0;
// Pre-allocate the array for better performance
const result = new Array(Math.ceil(totalLength / cleanSize));
while (cursor < totalLength) {
// Extract segment from current cursor to cursor + size
// Note: slice handles out-of-bounds end indexes gracefully
result[targetIndex++] = collection.slice(cursor, (cursor += cleanSize));
}
return result;
}
Technical Details
The implementation relies on the Array.prototype.slice method. A key detail is how the loop progresses: the cursor is incremented by the cleanSize within the slice call itself (cursor += cleanSize). This ensures the next iteration starts exactly where the previous one ended.
By pre-allocating the result array with new Array(Math.ceil(...)), we provide a hint to the JavaScript engine about the expected size of the output, which can be more memory-efficient than dynamically resizing an array in a large loop.