Back to Blog
Python

Python List Repetition: The * Operator and Its Pitfalls

python list repetition: Learn how to repeat lists in Python with the * operator, understand shallow copy behavior, and choose the right approach for your use case.

list repetitionasterisk operatorshallow copyitertools.repeatlist comprehension
Illustration of Python list repetition showing a list being multiplied by an asterisk to create a longer list with repeated elements.

When you need to repeat a list in Python, the most direct approach is the * operator: my_list * n creates a new list with the original elements repeated n times. This is a common idiom, but its behavior with mutable elements often surprises developers. This article covers how python list repetition works, what it actually produces, and when to choose a different tool.

How the Asterisk Operator Repeats a List

The * operator on a list returns a new list that contains the original elements repeated a specified number of times. The original list is not modified; a fresh list object is created.

base = [1, 2, 3] repeated = base * 3 print(repeated) # [1, 2, 3, 1, 2, 3, 1, 2, 3] print(base) # [1, 2, 3]

The multiplication is commutative for lists: 3 * base produces the same result as base * 3. The operation is defined for an integer multiplier on either side. If the multiplier is zero or negative, the result is an empty list.

print(base * 0) # [] print(base * -2) # []

This behavior is consistent across Python versions and is part of the sequence protocol that lists implement.

What the Repeated List Actually Contains

Repeating a list with * performs a shallow copy of the elements. For immutable elements like integers, strings, or tuples, this is usually fine because the elements themselves cannot change. However, the new list holds references to the same objects as the original list, not deep copies.

Consider a list of lists:

matrix = [[1, 2], [3, 4]] repeated = matrix * 2 print(repeated) # [[1, 2], [3, 4], [1, 2], [3, 4]]

Both repeated[0] and repeated[2] point to the same inner list object. Modifying one modifies the other:

repeated[0].append(99) print(repeated) # [[1, 2, 99], [3, 4], [1, 2, 99], [3, 4]]

The original matrix also changes because the inner list is shared:

print(matrix) # [[1, 2, 99], [3, 4]]

This aliasing is the most common source of bugs when using * for list repetition.

Repeating Lists with Mutable Elements

If you need to create independent copies of mutable elements, the * operator is not the right tool. For a list of lists, a list comprehension with a copy of each sublist is safer:

matrix = [[1, 2], [3, 4]] independent = [row[:] for row in matrix] * 2 independent[0].append(99) print(independent) # [[1, 2, 99], [3, 4], [1, 2], [3, 4]]

Here row[:] creates a shallow copy of each inner list, so each repeated element is a distinct object. If the inner lists contain mutable objects themselves, you may need copy.deepcopy to avoid nested aliasing.

import copy nested = [[{'a': 1}], [{'b': 2}]] deep_copied = copy.deepcopy(nested) * 2 deep_copied[0][0]['a'] = 99 print(deep_copied) # [[{'a': 99}], [{'b': 2}], [{'a': 1}], [{'b': 2}]] print(nested) # [[{'a': 1}], [{'b': 2}]]

Deep copying is expensive and rarely needed unless the data structure is genuinely nested and you require full independence.

Alternatives: itertools.repeat and List Comprehensions

The * operator is not the only way to produce repeated elements. itertools.repeat creates an iterator that yields the same value repeatedly, but it does not build a list by itself. You must combine it with list() to materialize a list.

from itertools import repeat repeated = list(repeat(0, 5)) print(repeated) # [0, 0, 0, 0, 0]

For a single immutable value, repeat is convenient. For a list of mutable objects, repeat has the same aliasing problem as * because it repeats the same object reference.

inner = [1, 2] repeated = list(repeat(inner, 3)) repeated[0].append(99) print(repeated) # [[1, 2, 99], [1, 2, 99], [1, 2, 99]]

A list comprehension gives you more control when you need to create fresh objects for each repetition:

fresh = [[1, 2] for _ in range(3)] fresh[0].append(99) print(fresh) # [[1, 2, 99], [1, 2], [1, 2]]

Each iteration of the comprehension evaluates the expression anew, so [1, 2] creates a new list each time. This is the recommended way to repeat a mutable structure when independence matters.

Performance and Memory Behavior

The * operator builds the entire repeated list in memory at once. If you need a very large repeated sequence, this can consume significant memory. For example, [0] * 10_000_000 creates a list with ten million references to the same integer object. The list itself occupies memory proportional to its length, but the integer objects are shared.

When the repeated element is immutable, memory usage is limited to the list's reference array plus the single immutable object. When the element is mutable, the list holds references to the same object, so memory usage is still just the reference array. However, if you use a comprehension to create independent objects, memory usage grows with the number of distinct objects created.

For lazy evaluation, itertools.repeat avoids building the list at all. This is useful when you only need to iterate over the repeated sequence without storing it:

from itertools import repeat for value in repeat('x', 1000): # process value without storing all 1000 copies pass

The iterator yields one value at a time, so memory usage stays constant. If you later need a list, you can convert it, but the conversion itself allocates the full list.

When to Use Each Approach

Choose the * operator when you need a quick, readable way to repeat a list of immutable elements or when you intentionally want shared references to mutable objects. It is the most concise syntax and is optimized in CPython for sequences.

Use a list comprehension when you need independent copies of mutable elements. The comprehension is slightly more verbose but avoids the aliasing trap and makes the intent explicit.

Use itertools.repeat when you need to iterate over a repeated value without materializing a list, or when you want to pass a lazy sequence to another function that accepts iterables. It is also useful when the repetition count is extremely large and memory is a concern.

Common Mistakes and Edge Cases

One common mistake is assuming that * deep-copies the elements. As shown earlier, it does not. Another mistake is using * with a list of dictionaries or other mutable objects and then modifying one element, expecting others to remain unchanged.

Edge cases include multiplying by zero or a negative number, which yields an empty list. Multiplying by a non-integer raises a TypeError:

try: [1, 2] * 1.5 except TypeError as e: print(e) # can't multiply sequence by non-int of type 'float'

Also note that * works on any sequence, not just lists. Tuples and strings support the same operation, but the result type matches the original sequence. For a tuple, (1, 2) * 2 returns a tuple; for a string, 'ab' * 2 returns a string. The same shallow-copy semantics apply to tuples of mutable objects.

When working with a list of lists, the * operator is often used to create a matrix, but it produces rows that share the same inner list. A common workaround is to use a comprehension for the rows:

rows = 3 cols = 4 matrix = [[0] * cols for _ in range(rows)] matrix[0][0] = 1 print(matrix) # [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]

This creates distinct row lists, which is usually what you want for a matrix. The inner [0] * cols is safe because integers are immutable.

Understanding the difference between sharing references and creating independent objects is the key to using python list repetition correctly. The * operator is a powerful tool, but it is not a deep-copy mechanism. Knowing when to use it versus a comprehension or an iterator will help you avoid subtle bugs and write more predictable code.

python list repetition: Practical Usage and Code Examples | RYUSLOG DEV