Collection::partition
Splits a collection into two collections: items passing a truth test and items failing it.
public function partition(callable|string $key, mixed $operator = null, mixed $value = null): staticParameters
| Parameter | Type | Description |
|---|---|---|
$$key | callable|string | A truth-test callback receiving ($value, $key), or a key name when using the where-style operator form. |
$$operatoroptional | mixed= null | Comparison operator for the where-style form, or the value to compare when only two arguments are given. |
$$valueoptional | mixed= null | The value to compare against in the three-argument where-style form. |
Return value
Illuminate\Support\CollectionA collection of exactly two collections: index 0 holds the items that passed, index 1 the items that failed.
partition answers "which of these passed and which did not" in a single traversal. It returns a collection holding two collections — the passers first, the failures second — and is almost always used with PHP array destructuring, which works because a collection implements ArrayAccess.
When to reach for it
Whenever you would otherwise write filter() and then reject() with the same condition. Two passes become one, and the condition is written once so the two branches cannot drift apart. Typical jobs: valid rows versus rejected rows in an import, users to email versus users to skip, files to keep versus files to delete.
What trips people up
Both halves preserve the original keys, so $failed from a list will have gapped numeric indexes — call values() before serialising or before assuming index 0 exists. The return value is a collection of collections, so forgetting the destructuring and calling a method straight on the result operates on the wrapper, not on the items.
Beyond the callback there is a where-style form: pass a key, an operator and a value, and the split is made by comparison instead. On an Eloquent collection the returned halves are Eloquent collections too.
Example
When to use it
- Split imported rows into valid and invalid batches in one pass before saving.
- Separate users who can be notified from those who opted out.
- Divide files into ones to keep and ones to delete based on age.
Frequently asked questions
How do I read the two halves?
Destructure the result: [$passed, $failed] = $collection->partition(...). The passing items always come first because the return value is a collection of two collections.
Are the original keys kept?
Yes, both halves preserve the keys the items had, which leaves gaps in numeric indexes. Call values() on a half before encoding it as JSON.
Can I split without writing a closure?
Yes — pass a key, an operator and a value, as in partition('total', '>=', 100), and the comparison is done for you.
More examples
The where-style form
Passing a key, an operator and a value avoids writing a closure for a simple comparison. B and C land in the passing half; A is the only failure.
<?php
$orders = collect([
['ref' => 'A', 'total' => 40],
['ref' => 'B', 'total' => 120],
['ref' => 'C', 'total' => 250],
]);
[$large, $small] = $orders->partition('total', '>=', 100);
echo $large->pluck('ref')->implode(', ') . PHP_EOL;
echo $small->pluck('ref')->implode(', ') . PHP_EOL;One pass instead of two
The same closure decides both halves, so the accept and reject rules cannot drift apart the way a separate filter() and reject() pair can. values() re-indexes the rejected rows so they encode as a JSON array.
<?php
$rows = collect([
['email' => '[email protected]'],
['email' => 'not-an-email'],
['email' => '[email protected]'],
]);
$isValid = fn (array $row) => filter_var($row['email'], FILTER_VALIDATE_EMAIL) !== false;
[$valid, $invalid] = $rows->partition($isValid);
echo 'accepted ' . $valid->count() . ', rejected ' . $invalid->count() . PHP_EOL;
print_r($invalid->values()->all());Acting on each half
The typical shape in application code: one branch is acted on, the other is recorded. It requires a booted application, so it is shown rather than executed.
<?php
[$subscribed, $unsubscribed] = $users->partition(
fn (User $user) => $user->accepts_marketing
);
Notification::send($subscribed, new WeeklyDigest);
Log::info('digest skipped', ['count' => $unsubscribed->count()]);
Discussion