Generating Permutations of Combinations
Generating Permutations of Combinations in PHP: Architecture, Performance, and Production Requirements
Permutation generation is a common algorithmic requirement in systems that need to evaluate every possible ordering of a selected set of values. Typical use cases include scheduling, configuration engines, search-space exploration, rule evaluation, optimization, test generation, and combinatorial data processing.
The technical requirement is straightforward:
Given a selected combination of elements, generate every possible ordering without repeating elements within the same permutation.
For a software project, however, the algorithm is only the first layer. A technical decision-maker should also require predictable complexity, input validation, duplicate handling, memory controls, test coverage, and clearly defined delivery criteria.
1. Combination vs. Permutation
A combination selects elements without considering their order.
A permutation considers order significant.
For example, the combination:
[10, 15, 20] can produce these permutations:
[10, 15, 20] [10, 20, 15] [15, 10, 20] [15, 20, 10] [20, 10, 15] [20, 15, 10] There are six results because:
3! = 3 × 2 × 1 = 6 This distinction should be documented in the technical specification. If the system treats order as meaningful, the implementation must generate permutations rather than combinations.
2. The Required Algorithm
A reusable permutation algorithm should accept an array and return all possible orderings.
function permute(array $items): array { // Generate every possible ordering. } The algorithm should not depend on the source of the data. It should work equally well with values retrieved from a database, API, configuration file, or user input.
This separation produces a cleaner architecture:
Data Source ↓ Validation ↓ Normalization ↓ Combination Selection ↓ Permutation Generator ↓ Output Transformation ↓ Application / API API means Application Programming Interface; it defines how software components exchange data and functionality.
3. Recursive Permutation Generation
Recursion is a natural fit because a permutation can be constructed by selecting one item and recursively arranging the remaining items.
For:
[10, 15, 20] the algorithm can first select 10.
The remaining values are:
[15, 20] The algorithm recursively generates:
[15, 20] [20, 15] Then 10 is added to the beginning:
[10, 15, 20] [10, 20, 15] The same process is repeated by starting with 15 and then 20.
4. A Reusable PHP Implementation
A clear recursive implementation is:
function permute(array $items): array { if (count($items) <= 1) { return [$items]; } $result = []; foreach ($items as $index => $item) { $remaining = $items; unset($remaining[$index]); $remaining = array_values($remaining); foreach (permute($remaining) as $permutation) { array_unshift($permutation, $item); $result[] = $permutation; } } return $result; } The implementation has three important components:
- A base case for arrays containing zero or one item.
- A loop that selects each possible first element.
- A recursive call that generates permutations for the remaining elements.
5. The Base Case
The base case prevents infinite recursion.
if (count($items) <= 1) { return [$items]; } When one item remains, there is only one possible ordering.
For:
[20] the only permutation is:
[20] Returning the result in a consistent nested-array structure makes it possible for the parent recursive call to process it uniformly.
6. Selecting the First Element
The central recursive operation is:
foreach ($items as $index => $item) { // Treat $item as the first element. } Each element gets an opportunity to become the first element of the permutation.
For:
[10, 15, 20] the algorithm creates three branches:
10 → remaining [15, 20] 15 → remaining [10, 20] 20 → remaining [10, 15] Each branch recursively solves a smaller version of the same problem.
7. Removing the Selected Element
After selecting an item, it must be removed from the remaining dataset:
$remaining = $items; unset($remaining[$index]); $remaining = array_values($remaining); This ensures that the same array element is not selected twice within the same permutation.
The recursive structure therefore becomes:
Choose one ↓ Remove it ↓ Permute the remainder ↓ Prepend the chosen item ↓ Store result 8. Combining Selection and Permutation
In a larger application, permutation generation often happens after combination generation.
For example:
$items = [3, 10, 15, 20]; for ($length = 2; $length <= count($items); $length++) { $combinations = combine($items, $length); foreach ($combinations as $combination) { $permutations = permute($combination); foreach ($permutations as $permutation) { echo implode('', $permutation) . PHP_EOL; } } } This architecture has a useful separation of responsibilities:
| Component | Responsibility |
|---|---|
| Combination generator | Selects which elements participate. |
| Permutation generator | Determines every possible ordering. |
| Formatter | Converts arrays into strings or application-specific structures. |
| API layer | Exposes the functionality to other software. |
| Storage layer | Persists results when persistence is actually required. |
9. Performance Is the Primary Production Concern
Permutation algorithms become expensive very quickly.
The number of permutations for n unique elements is:
n! The growth looks like this:
| Elements | Permutations |
|---|---|
| 3 | 6 |
| 4 | 24 |
| 5 | 120 |
| 6 | 720 |
| 7 | 5,040 |
| 8 | 40,320 |
| 10 | 3,628,800 |
Performance means how efficiently a system uses computational resources such as CPU time and memory.
A development team should therefore never describe a permutation feature simply as “fast” without specifying the expected input range.
10. The Input Limit Must Be Explicit
A production API should have an input limit.
For example:
if (count($items) > 10) { throw new InvalidArgumentException( 'Too many items for permutation generation.' ); } The exact limit depends on the application's hardware, latency requirements, output format, and acceptable processing time.
The important requirement is that the limit exists and is documented.
Without a limit, a perfectly valid request can become a resource-exhaustion problem.
11. Memory Management
The example implementation returns every permutation as an array.
$permutations = permute($items); This is convenient for small datasets but potentially expensive for large result sets.
If the consumer only needs to process one permutation at a time, a generator can be a better architectural choice.
PHP supports generators through the yield keyword:
function generatePermutations(array $items): Generator { // Yield one result at a time. } This avoids requiring the entire result set to remain in memory simultaneously.
Memory efficiency means reducing the amount of RAM required to perform the operation.
A technical team should determine whether the application requires:
- All results simultaneously.
- Streaming results.
- Pagination.
- Background processing.
- Immediate processing without persistence.
12. Duplicate Input Requires a Defined Policy
Consider:
$items = [10, 15, 10]; A naive permutation algorithm treats the two occurrences of 10 as separate positions. This can produce duplicate logical outputs.
If duplicate values have no semantic meaning, normalize the input first:
$items = array_values(array_unique($items)); Then generate permutations.
If duplicates are meaningful, however, they should not automatically be removed.
The development specification should therefore state:
Duplicate policy: - Remove duplicate values before generation OR - Preserve duplicates and deduplicate generated output OR - Treat repeated positions as meaningful This decision should be made before implementation.
13. Do Not Put Database Queries Inside Recursion
A common architectural mistake is allowing the recursive algorithm to access the database.
A poor design might conceptually look like:
permute() ↓ database query ↓ permute() ↓ database query ↓ permute() This multiplies I/O operations unnecessarily.
A better design is:
Database ↓ Fetch required data once ↓ Validate / normalize ↓ Permutation algorithm ↓ Persist or return results I/O means input/output operations such as database access, file operations, or network requests.
Keeping I/O outside the algorithm makes performance easier to predict and testing significantly easier.
14. API Requirements for a Production Implementation
If permutation generation is exposed through an API, the endpoint should define its contract explicitly.
A conceptual request might contain:
{ "items": [3, 10, 15, 20], "length": 3 } The response could contain generated values or a job identifier depending on the expected result size.
For small datasets:
{ "data": [ [3, 10, 15], [3, 15, 10] ] } For large datasets, synchronous responses may be inappropriate.
Instead:
POST /permutations ↓ Create processing job ↓ Return job ID ↓ Background worker generates results ↓ Client retrieves status/results This architecture prevents a long-running calculation from occupying an HTTP request indefinitely.
15. Suggested SLA
SLA means Service Level Agreement; it defines measurable service expectations such as availability, response time, and support commitments.
For a small internal permutation service, a practical initial SLA might specify:
| Metric | Suggested Requirement |
|---|---|
| API availability | 99.5% monthly for production service |
| Small requests | 95% completed within 500 ms |
| Large requests | Processed asynchronously |
| Input validation | 100% of invalid requests rejected safely |
| Error logging | All server-side failures recorded |
| Automated tests | Required before production deployment |
These values are starting points, not universal requirements. The actual SLA should be based on business impact and measured workload.
16. What the Development Team Should Deliver
A technical decision-maker should request explicit deliverables rather than accepting “the feature is finished.”
Required Deliverables
- Reusable PHP permutation function.
- Combination-to-permutation integration.
- Input validation.
- Duplicate-handling policy.
- Maximum input-size policy.
- Unit tests.
- Performance test results.
- API documentation if externally exposed.
- Error-handling documentation.
- Deployment instructions.
For production systems, also request:
- Monitoring configuration.
- Logging strategy.
- Rate limiting.
- Authentication where required.
- Background processing for large workloads.
17. Testing Requirements
Tests should cover both correctness and operational limits.
$items = [10, 15, 20]; $result = permute($items); assert(count($result) === 6); Additional test cases should include:
[] [10] [10, 15] [10, 15, 20] [10, 15, 10] `` Also test invalid input:
negative length excessive input size unexpected data types null values empty requests For performance testing, the team should record:
- Input size.
- Number of generated permutations.
- Execution time.
- Peak memory.
- HTTP response time when using an API.
18. Red Flags When Evaluating a Development Team
Red Flag: No Complexity Discussion
If the team cannot explain factorial growth, the implementation has not been properly evaluated.
Red Flag: No Input Limit
Unlimited factorial workloads are not an acceptable production API design.
Red Flag: Everything Runs Synchronously
Large workloads should generally be moved to background processing rather than blocking an HTTP request.
Red Flag: No Tests
Permutation algorithms are highly suitable for deterministic automated tests. There is little justification for shipping them without tests.
Red Flag: Database Access Inside Recursion
This usually indicates poor separation of concerns and can create unnecessary performance costs.
Red Flag: “Optimized” Without Measurements
Optimization decisions should be supported by benchmarks or profiling rather than assumptions.
19. Questions the Technical Decision-Maker Should Ask
- What is the maximum supported input size?
- How many results can that input produce?
- What happens when duplicate input exists?
- Does the implementation preserve deterministic ordering?
- Are results stored in memory or streamed?
- When does processing become asynchronous?
- What are the expected response-time targets?
- What happens when generation fails halfway through?
- Can the process be retried safely?
- How is resource consumption monitored?
- Which automated tests are included?
- What performance benchmark was used for acceptance?
20. Senior Developer Insight
The difficult part of permutation generation is rarely writing the recursive function. The difficult part is controlling the size of the problem.
A technically correct algorithm can still be unsuitable for production if it accepts unlimited input, stores millions of results in memory, or executes inside a synchronous web request.
The right architecture therefore treats permutation generation as a controlled processing workload:
Validate Input ↓ Normalize / Deduplicate ↓ Calculate Expected Result Volume ↓ ├── Small → Generate Immediately │ └── Large → Queue / Worker ↓ Generate ↓ Persist / Stream ↓ Return Results A worker is a background process that executes queued tasks outside the user's immediate HTTP request.
This approach gives the development team clear operational boundaries. It also gives the business owner something measurable to approve: maximum input size, expected processing time, memory limits, error behavior, and delivery artifacts.
21. Recommended Production Specification
A concise technical requirement could state:
Permutation Service Requirements 1. Accept an array of validated values. 2. Support configurable permutation length. 3. Generate unique orderings according to the defined duplicate policy. 4. Keep algorithmic logic independent from database and API layers. 5. Reject unsupported input sizes. 6. Return small workloads synchronously. 7. Process large workloads asynchronously. 8. Provide deterministic automated tests. 9. Record execution time and resource usage. 10. Document API, limits, errors, and deployment procedures. This specification is more useful than simply asking a developer to “create a permutation function.” It defines what production-ready means.
Conclusion
Generating permutations from combinations is a well-defined algorithmic problem, and PHP provides everything required to implement the core logic without introducing unnecessary dependencies.
The basic recursive model is simple: select an element, remove it from the remaining set, recursively permute the remainder, and prepend the selected element to each result.
The production decision is more involved.
Before approving the implementation, require explicit rules for duplicates, input limits, memory usage, response times, asynchronous processing, testing, and API behavior. Calculate the factorial growth of the workload before deployment rather than discovering it through production failures.
The correct question for a development team is therefore not simply “Can you generate permutations?”
It is:
“Can you generate the required permutations within defined performance, memory, reliability, and API constraints—and can you demonstrate that with tests and measurable benchmarks?”
That is the difference between an algorithm that works and a technical component that is ready for production.
