Collection::chunk
Splits the collection into a collection of smaller collections of the given size.
chunk(int $size, bool $preserveKeys = true): Collection- Added in Laravel 5.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$$size | int | Maximum number of items in each chunk. Values of 0 or below return an empty collection. |
$$preserveKeysoptional | bool= true | Keep the original keys inside each chunk. Pass false to renumber each chunk from zero. |
Return value
Collection<int, Collection>A new collection whose items are collections of at most $size items each; empty when $size is zero or negative.
Changelog
- PHP 12.xA second $preserveKeys argument was added, defaulting to true. Laravel 11 and earlier always preserved keys with no way to opt out.
chunk() slices the collection into fixed-size groups and returns a collection of collections. The outer collection is always a zero-indexed list; the inner ones are where the surprise lives.
Keys are preserved by default
By default each chunk keeps the keys the items had in the original. Chunk [1,2,3,4,5] by two and the second chunk is [2 => 3, 3 => 4], not [0 => 3, 1 => 4]. That bites in two places: $chunk[0] is undefined in every chunk after the first, and json_encode() serialises those chunks as objects rather than arrays. Laravel 12 added preserveKeys: false to renumber each chunk from zero; on Laravel 11 and earlier, call ->values() first or map values() over the chunks.
Grids and batching
The classic use is rendering a grid: chunk(3) in a Blade loop produces one row per chunk. The other is batching side effects — inserting a thousand rows in blocks of two hundred, or sending mail in groups small enough for a rate limit.
Memory
chunk() materialises everything, so it does not help with a large result set. For that, use the query builder's chunk(), chunkById() or lazy(), which fetch a page at a time from the database.
Example
When to use it
- Render a product grid three cards to a row in a Blade template.
- Insert ten thousand rows in batches of five hundred to stay under a query size limit.
- Split a list of recipients into groups small enough for a provider's per-request limit.
Frequently asked questions
Why is $chunk[0] undefined in a Laravel chunk?
Because chunk() preserves the original keys inside each chunk by default, so the second chunk of a five-item list holds keys 2 and 3, not 0 and 1. Pass preserveKeys: false on Laravel 12+, or call values() on each chunk.
How do I chunk a collection without preserving keys?
Pass false as the second argument: $collection->chunk(3, preserveKeys: false), available from Laravel 12. On earlier versions call values() on the collection first, or map values() over the resulting chunks.
Does chunk() reduce memory usage on a large query?
No — the whole collection is already in memory before chunk() runs. Use the query builder's chunk(), chunkById() or lazy() to fetch records a page at a time from the database instead.
More examples
A three-column grid in Blade
Each chunk becomes one grid row and the inner loop renders the cells. Because the inner foreach iterates values rather than indexing by position, preserved keys cause no trouble here — the problem only appears when you reach for $row[0]. A partial final row simply renders fewer cells. Needs a booted application with a view layer.
@foreach ($products->chunk(3) as $row)
<div class="row">
@foreach ($row as $product)
<div class="col-4">{{ $product->name }}</div>
@endforeach
</div>
@endforeachBatched inserts and the indexing trap
The insert loop keeps each statement to 200 rows. The second block shows why $chunk[0] fails: the second chunk holds keys 2 and 3, so has(0) is false and only first() or the true key reaches the item. The DB portion needs a booted application.
<?php
$rows = collect(range(1, 1000))->map(fn (int $i) => ['n' => $i]);
foreach ($rows->chunk(200) as $batch) {
// toArray() is required: insert() will not take a Collection.
DB::table('numbers')->insert($batch->toArray());
}
// The indexing trap, shown plainly:
$chunks = collect(['a', 'b', 'c', 'd'])->chunk(2);
var_dump($chunks[1]->has(0)); // bool(false) — key 0 is not in chunk 1
var_dump($chunks[1]->first()); // string(1) "c" — first() ignores keys
var_dump($chunks[1][2]); // string(1) "c" — the real key is 2
Discussion