# RFC: array_partition()

- Status: Under discussion
- Authors: Ayoub Ezzini, ayoub@ezzini.ma
- Date: 2026-10-25
- Version: 0.1
- Implementation: https://github.com/php/php-src/pull/24568
- Original: https://wiki.php.net/rfc/array_partition
- Page: https://externals.io/rfc/array_partition
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

## Votes

### Add array_partition() as outlined in the RFC? (closed)

Yes: 0 · No: 0 · Abstain: 0

## Discussions on the mailing list

- [\[RFC\] array_partition()](https://externals.io/message/132923.md) — started on 2026-10-25, 1 messages

---

## Introduction

Splitting an array into the elements that satisfy a condition and the elements that do not is a common operation. PHP has no built-in function for it, so code today does one of the following:

```php
// Two passes, with the condition duplicated and negated
$adults = array_filter($users, fn($u) => $u->age >= 18);
$minors = array_filter($users, fn($u) => $u->age < 18);
 
// One filter plus a set difference on the keys
$adults = array_filter($users, fn($u) => $u->age >= 18);
$minors = array_diff_key($users, $adults);
 
// A hand-written loop
$adults = [];
$minors = [];
foreach ($users as $key => $user) {
    if ($user->age >= 18) {
        $adults[$key] = $user;
    } else {
        $minors[$key] = $user;
    }
}
```

The first form repeats the condition and calls the callback twice per element. The second relies on a less obvious idiom. The third is correct but verbose for a simple intent.

This RFC proposes a single function that states the intent directly:

```php
[$adults, $minors] = array_partition($users, fn($u) => $u->age >= 18);
```

## Proposal

Add the following function to ext/standard:

```php
function array_partition(array $array, callable $callback): array {}
```

The callback is called once for each element, in array order, and receives the value and the key, like `array_find()`, `array_find_key()`, `array_any()` and `array_all()`. The function returns a list of exactly two arrays:

- index `0`: the elements for which the callback returned a truthy value;
- index `1`: all other elements.

Keys are preserved in both arrays, and the relative order of the elements is kept. The input array is not modified.

### Examples

```php
var_dump(array_partition([1, 2, 3, 4, 5], fn($v) => $v % 2 === 1));
// [[0 => 1, 2 => 3, 4 => 5], [1 => 2, 3 => 4]]
 
[$named, $indexed] = array_partition(
    ['a' => 1, 'b' => 2, 5 => 3, 6 => 4],
    fn($value, $key) => is_string($key),
);
// $named   = ['a' => 1, 'b' => 2]
// $indexed = [5 => 3, 6 => 4]
 
var_dump(array_partition([], fn($v) => true));
// [[], []]
```

### Semantics in detail

- The return value of the callback is interpreted with the usual boolean conversion, as in `array_filter()`.
- `array_partition($a, $cb)[0]` is identical (`===`) to `array_filter($a, $cb, ARRAY_FILTER_USE_BOTH)`, and `array_partition($a, $cb)[1]` is identical to `array_diff_key($a, array_partition($a, $cb)[0])`.
- An empty input returns `], [`, so the result can always be destructured into two variables.
- If the callback throws, the exception is propagated, no further elements are visited and no partial result is returned.
- By-reference elements of the input are handled in the same way as in `array_filter()`.
- There is no `$mode` parameter. The callback always receives both the value and the key, following `array_find()` rather than `array_filter()`. A callback that accepts only the value works unchanged, because userland functions ignore extra arguments.

Like `array_find()`, passing an internal function with a fixed arity as the callback fails, because it receives two arguments:

```php
array_partition(['x'], 'is_string');
// ArgumentCountError: is_string() expects exactly 1 argument, 2 given
 
array_partition(['x'], is_string(...));  // same error
array_partition(['x'], fn($v) => is_string($v));  // works
```

### Why the matching elements come first

Every prior-art implementation listed below returns the matching elements first. That order also reads naturally in the common destructuring form `[$matching, $rest] = array_partition(...)`.

A result keyed by `true` and `false` was suggested during the discussion. PHP casts boolean array keys to integers (`true` becomes `1` and `false` becomes `0`), so such a result would have to put the *non-matching* elements at index `0`. Whichever layout is chosen, one of the two idioms silently returns the wrong bucket. This RFC keeps the order used by other languages. Note that, with the proposed layout, the following does **not** work as it reads:

```php
// Wrong: true is cast to 1, so $passed receives the NON-matching elements
[true => $passed, false => $rejected] = array_partition($values, $callback);
 
// Correct
[$passed, $rejected] = array_partition($values, $callback);
```

### Why not a general grouping function?

A function that groups elements by an arbitrary key returned by the callback is more general, and is already the subject of a separate RFC (see “Related RFCs”). It does not replace a two-way split:

- The buckets are only created when at least one element falls into them, so destructuring needs a fallback (`$groups[true] ?? []`) for each side.
- Boolean callback results would have to be used as keys, which leads to the casting problem described above.
- The languages listed below that have both concepts provide them as separate functions (for example Kotlin `partition` and `groupBy`, Rust `partition` and the itertools `group_by`, Laravel `partition` and `groupBy`).

The two proposals are complementary, and this RFC does not block a future grouping function.

### Parameter naming

The parameters are named `$array` and `$callback`, as for the other callback-taking array functions (`array_filter()`, `array_map()`, `array_walk()`, `array_find()`, `array_find_key()`, `array_any()`, `array_all()`). `$predicate` was suggested as a more descriptive name. It was not chosen, so that the `callback:` named argument works the same way across the whole family.

## Prior art

- **Laravel**: `Illuminate\Support\Collection::partition()` returns two collections, passing first. The callback receives the value and the key, and keys are preserved.
- **Ruby**: `Enumerable#partition` returns `[true_elements, false_elements]`.
- **Kotlin**: `Iterable<T>.partition()` returns a `Pair` whose `first` holds the elements for which the predicate is true.
- **Rust**: `Iterator::partition()` returns a tuple `(matching, non_matching)`.
- **Haskell**: `Data.List.partition` returns a pair `(matching, non_matching)`.
- **JavaScript**: an `Array.prototype.partition` proposal was published in 2020 (<https://github.com/joaolucasl/proposal-array-prototype-partition>, discussion at <https://es.discourse.group/t/array-prototype-partition-like-filter-but-two-arrays-for-true-and-false/447>) but did not advance. ECMAScript 2024 added the general `Object.groupBy()` and `Map.groupBy()` instead. Lodash provides `_.partition()`.

## Related RFCs

- [Introduce array\_group and array\_group\_pair grouping functions](https://externals.io/rfc/array_group) (2023, Draft, never voted). It proposes general grouping, which is complementary to this RFC (see “Why not a general grouping function?”).
- [array\_find](https://externals.io/rfc/array_find) (PHP 8.4). This RFC follows its callback signature.

## Backward Incompatible Changes

The function name `array_partition` is taken in the global namespace. Code that declares its own global `array_partition()` without a `function_exists()` guard fails with “Cannot redeclare function array\_partition()” and must rename or namespace it.

Existing userland functions with this name often use a different meaning: they split an array into N chunks.

- The `array-partition` WordPress plugin by coffee2code defined a global `array_partition($array, $number_of_columns)` in version 1.0, guarded by `function_exists()` from 1.0.2. It was renamed to `c2c_array_partition()` in 1.1 and the deprecated name was removed in 1.3 (2020-09-10). Sites running a version older than 1.3 would no longer fatal on declaration because of the guard. However, calls such as `array_partition($posts, 3)` would reach the native function and throw a `TypeError`.
- php-src issue [\#14582](https://github.com/php/php-src/issues/14582) (June 2024) requested an `array_partition()` that splits an array into a given number of parts, like `array_chunk()` but by count. It was closed by its author, who noted that `array_chunk($array, (int) ceil(count($array) / $n))` covers the use case.

The name was kept because “partition” by predicate is the established meaning across the languages listed under “Prior art”, and because the N-chunks operation is already available through `array_chunk()`. The documentation should state the difference explicitly.

## Proposed PHP Version(s)

Next minor version (PHP 8.7).

## RFC Impact

### To the Ecosystem

IDEs, language servers and static analysers need to add the new function to their stubs. A precise return type can be expressed as `array{0: array<K, V>, 1: array<K, V>}` for an input of type `array<K, V>`. Polyfills are straightforward, because the behaviour can be reproduced with `array_filter()` and `array_diff_key()`, or with a `foreach` loop.

### To Existing Extensions

None. Only ext/standard gains a function. `array_filter()` and all other existing array functions are unchanged.

### To SAPIs

None.

## Performance

The value of this function is clarity, not speed: the cost is dominated by invoking the callback. Measured on a release build (`./configure --disable-all`, `php -n`), Apple M3 Pro under Docker (aarch64), callback `fn($v) => $v % 2 === 0`, median of 21 runs over 3 rounds, 1,000,000 elements:

| Approach | Packed int list | String keys |
|---|---|---|
| `array_partition()` | 40.2 - 42.2 ms | 42.1 - 45.3 ms |
| `array_filter()` + `array_diff_key()` | 39.8 - 40.4 ms | 42.8 - 44.6 ms |
| `array_filter()` twice | 88.5 ms | 90.3 ms |
| `foreach` loop | 34.8 - 35.7 ms | 40.4 - 45.4 ms |

`array_partition()` is on par with `array_filter()` + `array_diff_key()` (within about 2%), about 2.2 times faster than filtering twice, and about 5 - 12% slower than a hand-written loop, which avoids the function call per element. The benchmark script and the raw output are available on request.

## Open Issues

None at this time.

## Future Scope

- Support for `iterable` or generators (for example accepting a `Traversable` and returning two arrays). This RFC follows the other `array_*` functions and only accepts arrays.
- A general grouping function, as proposed by the array\_group RFC.

## Voting Choices

Primary Vote requiring a 2/3 majority to accept the RFC:

**Vote: Add array\_partition() as outlined in the RFC?** — Yes: 0 · No: 0 · Abstain: 0 (closed)

## Patches and Tests

<https://github.com/php/php-src/pull/24568>

## Implementation

After the RFC is implemented, this section should contain:

1. the version(s) it was merged into
2. a link to the git commit(s)
3. a link to the PHP manual entry for the feature

## References

- Discussion on internals: <https://news-web.php.net/php.internals/132923>
- array\_find RFC: [https://wiki.php.net/rfc/array\_find](https://externals.io/rfc/array_find)
- array\_group RFC: [https://wiki.php.net/rfc/array\_group](https://externals.io/rfc/array_group)

## Rejected Features

- A `$mode` parameter mirroring `array_filter()` (`ARRAY_FILTER_USE_KEY` / `ARRAY_FILTER_USE_BOTH`): removed after review in favour of the `array_find()` signature.
- A result keyed by `true` / `false`: see “Why the matching elements come first”.
- Naming the callback parameter `$predicate`: see “Parameter naming”.

## Changelog

- 0.1 (2026-10-25): Initial draft.
