array_partition() View as Markdown

Under discussion
▴ 0 ▾ 0
community vote
Authors
Ayoub Ezzini, ayoub@ezzini.ma
Date
2026-10-25
Version
0.1
Implementation
https://github.com/php/php-src/pull/24568
Source
wiki.php.net/rfc/array_partition

Vote

Add array_partition() as outlined in the RFC? no votes
Yes · No · Abstain

Discussions on the mailing list

[RFC] array_partition()
by Ayoub EZZINI · 1 message

RFC text

Copied from wiki.php.net, last synchronized Oct 25, 2026 14:46 UTC

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:

// 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:

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

Proposal

Add the following function to ext/standard:

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

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:

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:

// 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

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 (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

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.