Building Combinations of Array Elements
Building Combinations of Array Elements in PHP: A Practical Guide for Real Projects
When you are building a small application, e-commerce feature, pricing calculator, product configurator, search tool, or data-processing script, generating combinations can look like a simple programming task. It usually is—until the requirements change.
At first, you may only need combinations of two values. Then someone asks for three values. Then four. Then the array becomes dynamic. Then duplicate values appear. Then order suddenly matters. If the code was built around hard-coded loops, you can end up spending more time rewriting the algorithm than building the actual product.
The practical solution is to build a reusable combination generator from the beginning.
This guide explains how to generate combinations of different lengths in PHP, when recursion is worth using, what you should build yourself before paying for a library or service, and where developers commonly waste time and budget.
1. Start With the Actual Requirement
Before writing PHP, define what “combination” means for your application.
Suppose you have:
$items = [3, 10, 15, 20]; You might want to select:
- 2 items at a time
- 3 items at a time
- 4 items at a time
For combinations, order does not matter.
Therefore:
[3, 10] and:
[10, 3] represent the same combination.
This is different from permutations, where those two arrangements are separate results.
That distinction should be written into the technical specification before implementation. It can save hours of debugging later.
2. The Cheapest Approach: Build the Small Version Yourself
If your requirement is simply to generate combinations from a PHP array, you probably do not need an external API, paid service, or large dependency.
For a small internal tool, the first version can be built with standard PHP.
A basic two-item implementation could use nested loops:
$items = [3, 10, 15, 20]; for ($i = 0; $i < count($items); $i++) { for ($j = $i + 1; $j < count($items); $j++) { echo $items[$i] . '-' . $items[$j] . PHP_EOL; } } This works.
But there is an obvious problem: it only solves one specific case.
If tomorrow you need combinations of three items, you need another loop.
for ($i = 0; $i < count($items); $i++) { for ($j = $i + 1; $j < count($items); $j++) { for ($k = $j + 1; $k < count($items); $k++) { // Three-item combination } } } Then four items require another loop.
This is where a reusable recursive solution becomes financially useful: you write the logic once instead of maintaining a growing collection of special cases.
3. What a Reusable Combination Generator Should Accept
A good helper function should not care whether your application is an e-commerce site, an analytics dashboard, or a command-line script.
It should accept:
- The source array.
- The desired combination length.
Conceptually:
combine($items, $length); For example:
$items = [3, 10, 15, 20]; $combinations = combine($items, 2); Then the same function can handle:
combine($items, 3); combine($items, 4); This is much more maintainable than creating separate functions for every possible size.
4. The Core Idea Behind Recursion
Recursion sounds complicated until you reduce it to one question:
How do I build a combination by choosing one element and asking the same function to complete the rest?
For example, with:
[3, 10, 15, 20] and a desired length of 2, the algorithm can:
- Choose
3. - Look at the remaining values.
- Choose one value from those remaining values.
- Produce
[3, 10],[3, 15], and[3, 20].
Then it moves to 10:
[10, 15] [10, 20] Then:
[15, 20] The crucial detail is that the recursive call only considers elements after the currently selected index.
That prevents generating:
[3, 10] [10, 3] as separate combinations.
5. A Reusable PHP Implementation
A practical implementation can look like this:
function combine(array $items, int $length): array { $result = []; if ($length === 0) { return [[]]; } if ($length > count($items)) { return []; } foreach ($items as $index => $item) { $remaining = array_slice($items, $index + 1); foreach (combine($remaining, $length - 1) as $combination) { array_unshift($combination, $item); $result[] = $combination; } } return $result; } The function can now work with different combination sizes without changing the algorithm.
6. Using the Function
For two-item combinations:
$items = [3, 10, 15, 20]; $result = combine($items, 2); print_r($result); For three:
$result = combine($items, 3); For all sizes from two through the full array:
for ($length = 2; $length <= count($items); $length++) { $combinations = combine($items, $length); foreach ($combinations as $combination) { print_r($combination); } } This is the point where a small reusable function starts paying for itself.
7. The Base Case Is the Part You Should Not Rush
Most recursion bugs come from incorrectly defining the stopping condition.
The key condition is:
if ($length === 0) { return [[]]; } Why return an array containing an empty array rather than simply returning an empty array?
Because the recursive algorithm needs to know that it has successfully completed one combination.
Conceptually:
choose item ↓ choose another item ↓ length becomes 0 ↓ combination is complete The base case is therefore not just a technical requirement. It represents the moment when the algorithm has finished constructing one valid result.
8. Add Validation Before You Spend Time Debugging
A production helper should decide how invalid requests behave.
For example:
combine([1, 2, 3], 5); You cannot select five unique elements from an array containing only three.
A sensible implementation returns an empty result:
if ($length > count($items)) { return []; } You should also decide what happens when the requested length is negative:
if ($length < 0) { throw new InvalidArgumentException( 'Combination length cannot be negative.' ); } These decisions are inexpensive to make early and expensive to discover after the function is integrated into a larger application.
9. The Difference Between Combination Generation and String Generation
Keep the combination algorithm separate from the final output format.
For example, your generator should produce:
[ [3, 10], [3, 15], [3, 20], [10, 15], [10, 20], [15, 20] ] Another layer can convert these into strings:
310 315 320 1015 1020 1520 This separation matters.
If tomorrow you need:
"3-10" "3-15" "3-20" you should not have to rewrite your combination algorithm.
10. A Simple Transformation Layer
You can transform combinations after they have been generated:
$results = []; foreach ($combinations as $combination) { $results[] = implode('', $combination); } Or use another separator:
$results[] = implode('-', $combination); The generator remains independent of the presentation format.
This is particularly useful when the same logic eventually serves an API, web interface, database process, or command-line tool.
11. Do Not Build a Paid Solution Too Early
If you are developing a small business application, budget discipline matters.
For this particular problem, the cost-effective progression is:
| Stage | Recommended Action | Typical Cost |
|---|---|---|
| Prototype | Write a small PHP helper | $0 |
| Validation | Add test cases and edge cases | $0 |
| Integration | Connect it to the existing application | Usually existing development time |
| Optimization | Profile only if performance becomes an issue | $0–small development cost |
| Specialized scale | Consider external libraries or architectural changes | Project-dependent |
Do not buy infrastructure to solve a problem that standard PHP can solve in a few dozen lines.
12. When You Should Pay for Development
There is a point where doing everything yourself becomes false economy.
Consider delegating when:
- The combination algorithm is only one part of a larger production system.
- The application requires authentication, payments, database architecture, or deployment.
- The data volume is large enough to require profiling and optimization.
- The algorithm must run asynchronously in queues.
- The output is consumed by multiple services.
- The code requires security review.
Do not outsource because recursion looks unfamiliar. Outsource when the surrounding system creates real engineering complexity.
13. A Four-Week Practical Development Sequence
Week 1: Prove the Algorithm
Start with a local PHP script.
$items = [3, 10, 15, 20]; for ($length = 2; $length <= count($items); $length++) { $results = combine($items, $length); print_r($results); } Verify the output manually.
Do not connect databases, payment systems, hosting infrastructure, or external APIs yet.
Week 2: Add Tests
Test:
- Empty arrays.
- One-element arrays.
- Combination length of two.
- Combination length equal to the array size.
- Combination length larger than the array.
- Duplicate input.
- Different data types.
The goal is to discover whether the algorithm actually matches your requirements.
Week 3: Integrate
Only after the algorithm is stable should you connect it to the application's actual data source.
Keep data retrieval separate from combination generation:
$items = getItemsFromDatabase(); $items = normalizeItems($items); $combinations = combine($items, 3); This makes future changes cheaper.
Week 4: Measure Before Optimizing
If performance is acceptable, stop.
Do not optimize code simply because it looks theoretically slow.
If performance is not acceptable, measure:
- Execution time.
- Memory consumption.
- Input size.
- Number of generated combinations.
- Time spent transforming results.
Then optimize the actual bottleneck.
14. The Mathematics You Need to Know
The number of combinations when selecting r elements from n elements is:
C(n, r) = n! / (r!(n-r)!) For example, selecting two elements from four produces:
C(4, 2) = 6 Selecting three produces:
C(4, 3) = 4 Selecting all four produces:
C(4, 4) = 1 This gives you an important budgeting and architecture insight: the input size determines the potential workload.
Before asking a developer to “generate all combinations,” calculate how many results the system may need to produce.
15. Red Flags That Should Stop the Implementation
Hard-Coded Loops Everywhere
If you see separate functions for two, three, four, five, and six elements, stop and reconsider the architecture.
No Maximum Input Size
A feature that works with four elements may become unusable with hundreds.
Generating Results Without Measuring Volume
Always estimate the number of combinations before storing everything in memory.
Database Queries Inside Recursive Calls
Do not repeatedly query a database from inside the recursive algorithm unless there is a very specific architectural reason.
Mixing Output Formatting With Selection Logic
Keep selection, ordering, and formatting as separate concerns.
16. Questions to Ask Before Hiring Someone
If you are paying a developer to implement this feature, ask:
- Will the solution support arbitrary combination lengths?
- How will duplicates be handled?
- How are invalid combination lengths handled?
- Will the original array be modified?
- How many combinations can the current input produce?
- What happens when the input grows by 10×?
- Are there automated tests?
- Is the generation logic separated from database access?
- How will memory usage be controlled if the result set becomes large?
If the developer cannot answer these questions, the problem is not the complexity of recursion. The problem is insufficient technical planning.
17. Senior Developer Insight
Build the smallest reusable abstraction that solves the real requirement, not the smallest piece of code that produces today's output.
A nested loop is perfectly acceptable when you genuinely need exactly two-element combinations and the requirement will never change. But if the application needs 2, 3, 4, or arbitrary-length combinations, recursion provides a cleaner abstraction.
More importantly, do not confuse algorithmic elegance with production readiness.
A recursive function is only one component. A production implementation also needs:
Input validation ↓ Normalization ↓ Duplicate handling ↓ Combination generation ↓ Output transformation ↓ Performance limits ↓ Testing If the result set can become large, consider generating results incrementally instead of storing every combination in a large PHP array. In other words, the next optimization is often architectural rather than syntactic.
18. Final Build-vs-Buy Checklist
Before spending money on a solution, run this checklist:
| Question | If Yes | If No |
|---|---|---|
| Can standard PHP solve it? | Build a prototype yourself. | Research specialized tools. |
| Is the input small? | Keep the implementation simple. | Analyze scalability. |
| Is the combination length dynamic? | Use a reusable recursive solution. | A simple loop may be enough. |
| Can duplicates occur? | Define uniqueness before generation. | Keep the algorithm simpler. |
| Can output volume become large? | Calculate complexity and memory requirements. | Standard in-memory arrays may be sufficient. |
| Is the code going into production? | Add validation, tests, and monitoring. | Keep the prototype lightweight. |
Conclusion
Generating combinations in PHP is a good example of where a small amount of algorithmic planning can prevent unnecessary development costs.
For a fixed, tiny requirement, simple loops are acceptable. For dynamic combination lengths, a recursive helper is usually the better long-term choice. The implementation should remain independent from databases, UI code, and output formatting.
Start with standard PHP. Validate the concept. Test edge cases. Calculate the expected number of results. Integrate only after the algorithm is proven. Optimize only after measuring actual performance.
Most importantly, spend money where complexity actually exists. If the problem is a reusable combination generator, you can usually solve the core algorithm yourself. If the real project includes authentication, payments, data synchronization, deployment, security, or high-volume processing, that is where professional development time becomes valuable.
