2025, Oct 26 23:00

In-Place Matrix Transpose in Python: mutate lists instead of rebinding, with clear, import-free examples

Learn how to transpose a matrix in place in Python: fix rebinding vs mutation, use slice assignment and zip(*data), and update the original list without imports.

Transposing a matrix “in place” in Python often trips people up. The crux is that rebinding a local variable inside a function does not change the caller’s object, while mutating the object does. If you build a transposed copy and then assign it to the local parameter name, the original matrix outside the function remains untouched. Below is a walkthrough of the issue and several clean ways to perform an in-place transpose without imports.

Problem setup

The goal is to transpose a matrix so that the original variable changes. The attempt below prints the expected matrix inside the function, but the matrix outside the function remains the original.

def flip_grid(grid: list):
    alt = []
    for i in range(len(grid)):
        row = []
        for j in range(len(grid)):
            row.append(grid[j][i])
        alt.append(row)
    grid = []
    for i in range(len(alt)):
        row = []
        for j in range(len(alt)):
            row.append(alt[i][j])
        grid.append(row)
    print(grid)  # inside function

board = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flip_grid(board)
print(board)     # outside function

What’s going on and why

The name you pass into a function is bound to a local parameter. Inside the function, assigning grid = [] doesn’t mutate the original list; it just rebinds the local name grid to a new, empty list. The caller still holds the old list. To change the original matrix, you need to mutate the list object the caller passed in, not reassign the local variable.

Minimal change fix

To truly modify the same list object, clear it or use slice-assignment, then populate it. Replace the local rebinding with a mutating operation.

def flip_grid(grid: list):
    alt = []
    for i in range(len(grid)):
        row = []
        for j in range(len(grid)):
            row.append(grid[j][i])
        alt.append(row)
    # mutate instead of rebind
    grid.clear()          # or: grid[:] = []
    for i in range(len(alt)):
        row = []
        for j in range(len(alt)):
            row.append(alt[i][j])
        grid.append(row)

board = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flip_grid(board)
print(board)

This preserves the object identity of the list referenced by the caller and updates its contents to the transposed data.

A concise in-place variant

You can avoid the temporary copy by computing the transpose directly and replacing the contents via slice-assignment. Here are two compact forms.

def transpose_in_place(data: list):
    data[:] = [
        [row[col_idx] for row in data]
        for col_idx in range(len(data[0]))
    ]

Another succinct option achieves the same effect:

def transpose_in_place(data: list):
    data[:] = [list(col) for col in zip(*data)]

Why this matters

Understanding the difference between rebinding a name and mutating an object is fundamental in Python. It affects how you design APIs, reason about side effects, and debug functions that aim to modify their inputs. If your goal is to update the caller’s data, mutate the object in place; if your goal is to compute a new value, return it and let the caller rebind their variable. In some contexts, using a dedicated library is a better fit, but if you avoid imports for learning purposes, the in-place techniques shown above are both clear and effective.

Conclusion

To change the original matrix from within a function, don’t reassign the parameter name. Instead, mutate the underlying list, for example by clearing it or using slice-assignment, and then filling it with the transposed rows. If you prefer a functional approach, return the new matrix and rebind it at the call site. Either way, being explicit about mutation versus assignment will keep your matrix operations predictable and correct.

The article is based on a question from StackOverflow by Dominic and an answer by trincot.