2025, Sep 24 15:00
How to generate Python permutations with fixed positions using itertools.permutations
Python guide to permutations with fixed positions: pin indices, permute the rest, reinsert. Includes a function based on itertools.permutations with examples
Generating permutations is straightforward in Python, but real-world tasks often come with constraints: some positions must stay fixed while the rest are permuted. This guide shows how to handle a single fixed index and then generalize the approach to multiple fixed indices using itertools.permutations.
Problem setup: permute with a fixed index
Suppose you have a sequence and want to permute only the movable elements while keeping a specific index unchanged. Here is a minimal example that keeps the third element fixed and permutes the rest.
from itertools import permutations
nums = [5, 6, 7, 9]
pin_idx = 2  # keep 7 at index 2 and permute [5, 6, 9]
free_slice = nums[:pin_idx] + nums[pin_idx + 1:]
perm_iter = permutations(free_slice)
accum = []
for tup in perm_iter:
    accum.append(list(tup))
for candidate in accum:
    candidate.insert(pin_idx, nums[pin_idx])
print(accum)
This produces all permutations of the movable elements and then re-inserts the fixed value back into its original position. It works well for a single fixed index.
What’s actually happening and why it works
The core idea is simple and effective: remove values at fixed positions, generate permutations for everything else, and then reinsert the fixed values at their original indices. Since permutations only touch the reduced set, the elements at fixed indices never move, and all valid arrangements of the remaining values are explored.
When you scale this to multiple fixed indices, the same concept applies: isolate all indices that must remain untouched, permute only the remaining values, and reinsert the fixed ones back into each generated permutation.
Generalized solution for multiple fixed indices
The following function encapsulates this pattern for any set of fixed indices. It uses itertools.permutations to produce rearrangements for the non-fixed part and then inserts the fixed values back in place.
from itertools import permutations
def lock_positions_perms(items: list, pinned_pos: list[int] | None = None) -> list[list]:
    """
    Build all permutations of the input items while keeping the elements at
    specified positions unchanged.
    :param items: sequence to permute
    :param pinned_pos: indices that must remain at their original positions
    :return: list of permutations with pinned positions preserved
    """
    pinned_pos = [] if pinned_pos is None else pinned_pos
    movable_vals = [val for idx, val in enumerate(items) if idx not in pinned_pos]
    all_arrangements = [list(t) for t in permutations(movable_vals)]
    for arrangement in all_arrangements:
        for pos in pinned_pos:
            arrangement.insert(pos, items[pos])
    return all_arrangements
print(lock_positions_perms(["a", "b", "c", "d", "e"], pinned_pos=[1, 3]))
# [['a', 'b', 'c', 'd', 'e'], ['a', 'b', 'e', 'd', 'c'], ['c', 'b', 'a', 'd', 'e'],
#  ['c', 'b', 'e', 'd', 'a'], ['e', 'b', 'a', 'd', 'c'], ['e', 'b', 'c', 'd', 'a']]
The mechanics mirror the single-index version: extract the non-pinned values, generate all permutations of that subset, and insert the pinned values back at their original indices for each result.
Notes on behavior and trade-offs
As written, the function materializes the entire result set by consuming itertools.permutations eagerly. It can be adjusted to yield results one by one by inserting pinned values on the fly and using yield instead of returning the full list. Also, because reinsertion happens for every permutation produced, this approach may not be the most efficient. A more efficient alternative is not obvious without implementing a custom permutation routine.
Why this matters
Being able to permute with constraints is useful whenever a data layout has anchors that must not move, while the remaining elements can be explored exhaustively. This pattern keeps the code clear, lets you express constraints through indices, and leverages a well-tested standard-library tool: itertools.permutations.
Takeaways
When you need permutations with fixed positions, separate the fixed and movable parts, permute the latter, and reassemble the results by inserting the fixed values back into their original positions. For large outputs or streaming use, consider yielding results instead of building a full list. If performance under heavy reinsertion becomes a bottleneck, a fundamentally different permutation strategy would be required.
The article is based on a question from StackOverflow by Zeryab Hassan Kiani and an answer by simon.