In Swift, an efficient function that separates an array into 2 arrays based on a predicate
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Splitting an array into two arrays based on a predicate is a common task, and the naive solution is often wasteful. In Swift, the most practical implementation is a single pass that appends each element to either the matching bucket or the non-matching bucket while preserving the original order.
Use A Single Pass Partition Function
The simplest correct solution is also efficient. Walk the array once, evaluate the predicate once per element, and push the element into one of two result arrays.
This avoids the most common alternative, which is calling filter twice. Two filter calls are readable, but they perform two full passes and evaluate the predicate twice for every element.
Here is a reusable generic helper:
This solution has linear time complexity and preserves order inside each output array. Order preservation is often the deciding factor in real applications, especially when the input array already has semantic ordering.
Why Not Use partition(by:)
Swift also offers partition(by:), but it solves a slightly different problem. It rearranges the elements of a mutable collection in place and returns the boundary index between the two partitions.
That can be useful when mutation is acceptable and you want to avoid allocating two fresh arrays immediately. It does not preserve order, and the predicate semantics can surprise people because the method groups elements according to where the predicate returns false.
Example:
That code is valid, but it is usually not the best answer when the goal is "give me two arrays that preserve the original relative order." For that requirement, the manual loop is clearer and safer.
A Functional Variant With reduce(into:)
If you prefer a more functional style, reduce(into:) gives you the same single-pass behavior without creating unnecessary intermediate arrays.
This version is concise, but the explicit for loop is often easier to scan during maintenance. Efficiency is effectively the same, so pick the style your team reads fastest.
Complexity And Practical Tradeoffs
There are three common strategies:
- Two
filtercalls. - One pass with a loop.
- In-place
partition(by:).
Two filter calls are easy to write but perform extra work. The single-pass loop is usually the best all-around choice because it keeps the code obvious and avoids repeated predicate evaluation. partition(by:) can reduce allocations in some cases, but it changes element order and mutates the source array.
For most application code, the best definition of efficient is not just fewer operations. It is also predictable semantics. Developers often choose the one-pass function because they want a clear contract: same input order, one predicate evaluation per element, and no hidden mutation.
Common Pitfalls
The first pitfall is using filter twice with an expensive predicate. If the predicate hits the network, parses a date, or performs more than a trivial check, doubling the calls becomes noticeable.
The second pitfall is assuming partition(by:) is stable. It is not. If preserving original order matters, do not use it as a drop-in replacement for a manual split.
Another issue is over-reserving capacity. In the example above, both arrays reserve count, which is safe but slightly generous. It is usually fine because it avoids repeated growth and keeps the sample simple, but you should know that the total reserved space can exceed the final number of stored elements.
Finally, be clear about naming. A function named partition suggests behavior similar to the standard library method. Naming your helper split(by:) or grouped(by:) makes the semantics easier to infer.
Summary
- The most practical solution is a single pass that appends into two arrays.
- A manual loop preserves order and evaluates the predicate once per element.
- '
partition(by:)is in-place and fast, but it mutates the array and is not stable.' - '
reduce(into:)provides the same efficiency in a more functional style.' - Avoid two
filtercalls when the predicate is expensive or the array is large.

