Chunk array into batches
typescriptA generic TypeScript function that splits an array into smaller arrays (chunks) of a specified size, with proper handling for all edge cases.
Code
function chunk<T>(array: readonly T[], size: number): T[][] {
if (size <= 0) {
throw new RangeError('Chunk size must be a positive integer');
}
if (!Number.isInteger(size)) {
throw new TypeError('Chunk size must be an integer');
}
if (array.length === 0) {
return [];
}
const chunks: T[][] = [];
const totalChunks = Math.ceil(array.length / size);
for (let i = 0; i < totalChunks; i++) {
const start = i * size;
const end = Math.min(start + size, array.length);
chunks.push(array.slice(start, end) as T[]);
}
return chunks;
}
// Alternative implementation using Array.from
function chunkFunctional<T>(array: readonly T[], size: number): T[][] {
if (size <= 0) {
throw new RangeError('Chunk size must be a positive integer');
}
if (!Number.isInteger(size)) {
throw new TypeError('Chunk size must be an integer');
}
if (array.length === 0) {
return [];
}
return Array.from(
{ length: Math.ceil(array.length / size) },
(_, index) => array.slice(index * size, index * size + size) as T[]
);
}
// Usage examples
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.log('Original array:', numbers);
console.log('Chunks of 3:', chunk(numbers, 3));
console.log('Chunks of 4:', chunk(numbers, 4));
console.log('Chunks of 15:', chunk(numbers, 15));
console.log('Empty array:', chunk([], 5));
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' },
{ id: 4, name: 'Diana' },
{ id: 5, name: 'Eve' }
];
const userBatches = chunk(users, 2);
console.log('User batches:', userBatches);
// Type inference works correctly
const stringChunks: string[][] = chunk(['a', 'b', 'c', 'd', 'e'], 2);
console.log('String chunks:', stringChunks);How It Works
This chunk function uses TypeScript generics to maintain full type safety across any array type. The generic parameter T ensures that the return type T[][] correctly reflects an array of arrays containing the original element type. The readonly T[] parameter type allows the function to accept both regular arrays and readonly arrays, making it more flexible without sacrificing immutability guarantees.
The function implements comprehensive input validation before processing. It checks that the chunk size is both a positive number and an integer, throwing appropriate error types (RangeError for invalid values, TypeError for non-integers). This defensive approach prevents subtle bugs that could occur with floating-point sizes or negative numbers. The early return for empty arrays is a simple optimization that avoids unnecessary computation.
The core algorithm calculates the total number of chunks needed using Math.ceil(array.length / size), which correctly handles arrays whose length isn't evenly divisible by the chunk size. The loop then extracts each chunk using slice(), which has the convenient property of automatically clamping the end index to the array length. This means the final chunk will simply contain fewer elements if the array doesn't divide evenly, without requiring special handling.
The alternative functional implementation using Array.from demonstrates a more declarative approach. It creates a new array with the calculated length and uses the mapping function to generate each chunk. While this version is more concise and arguably more elegant, it may be slightly less performant for very large arrays due to the additional function call overhead per chunk.
Use this pattern when you need to process large datasets in batches (like API pagination or database bulk inserts), implement infinite scroll loading, or distribute work across multiple workers. Avoid using it in hot paths where you're chunking the same data repeatedly—consider caching the result or restructuring your data model instead.