Back to Blog
Python

Python List Concatenation: +, extend(), and Alternatives

python list concatenation: Learn how to concatenate lists in Python using +, extend(), unpacking, and itertools.chain, with tradeoffs in memory and mutability.

list concatenationextend methoditertools.chainlist unpackingPython lists
Diagram showing different Python list concatenation methods: plus operator, extend, and chain, with arrows indicating new list creation or in-place modification.

Python List Concatenation: +, extend(), and Alternatives

Concatenating lists is a common operation, but the way you combine lists in Python affects memory usage, mutability, and readability. The primary methods are the + operator, list.extend(), unpacking in a list literal, and itertools.chain. Each serves a different purpose, and choosing the wrong one can lead to unintended side effects or unnecessary copies.

The + Operator Creates a New List

The + operator returns a new list containing elements from both operands. The original lists remain unchanged.

a = [1, 2, 3] b = [4, 5] c = a + b print(c) # [1, 2, 3, 4, 5] print(a) # [1, 2, 3] print(b) # [4, 5]

This is the most readable approach when you need a new list and don't want to modify the inputs. However, it creates a full copy of both lists. If you concatenate large lists repeatedly, the memory overhead can become significant because each operation allocates a new list and copies all references.

extend() Modifies the List in Place

list.extend() appends all elements from an iterable to the existing list. It returns None and changes the original list.

a = [1, 2, 3] b = [4, 5] a.extend(b) print(a) # [1, 2, 3, 4, 5] print(b) # [4, 5] # unchanged

Use extend() when you want to accumulate results into an existing list without creating intermediate copies. This is often more memory-efficient than repeated + operations in a loop. Note that extend() accepts any iterable, not just lists, so you can append tuples, sets, or generator output.

Unpacking in a List Literal

Python 3.5 introduced extended iterable unpacking, which allows you to concatenate lists inside a list literal using the * syntax.

a = [1, 2, 3] b = [4, 5] c = [*a, *b] print(c) # [1, 2, 3, 4, 5]

This behaves like + in that it creates a new list, but it is more flexible. You can intersperse literal elements:

c = [0, *a, 10, *b]

The unpacking approach is concise and works with any iterable, not just lists. It is often preferred when you need to combine multiple lists or add constant elements in a single expression.

Using itertools.chain for Lazy Concatenation

When you need to iterate over the combined elements without materializing a new list, itertools.chain provides a lazy iterator.

from itertools import chain a = [1, 2, 3] b = [4, 5] for item in chain(a, b): print(item)

chain does not copy the elements; it yields them one by one from the underlying iterables. This is useful when you only need to traverse the combined sequence once, or when the lists are very large and you want to avoid the memory cost of a new list. If you actually need a list, you can call list(chain(a, b)), but that defeats the memory advantage.

Performance and Memory Tradeoffs

The key distinction is between operations that create a new list and those that modify an existing one. + and [*a, *b] allocate a new list and copy references from both inputs. extend() reuses the existing list's storage, potentially growing it in place, which avoids the allocation of a second list but still may reallocate the underlying array if capacity is exceeded.

Repeated + in a loop is a common anti-pattern:

result = [] for item in some_iterable: result = result + [item] # creates a new list each iteration

This is O(n²) in total because each concatenation copies all previously accumulated elements. Using extend() or append() inside the loop is linear:

result = [] for item in some_iterable: result.append(item)

If you must concatenate many lists at once, [*a, *b, *c] or list(chain(a, b, c)) are both clear, but chain avoids the intermediate copy if you only need to iterate.

Choosing the Right Approach for Your Use Case

The decision depends on whether you need to preserve the original lists and whether you want to avoid copying.

  • Use + when you want a new list and the operands are small or the copy cost is irrelevant.
  • Use extend() when you want to add elements to an existing list and don't need the original list to remain unchanged.
  • Use [*a, *b] when you need a new list and also want to include literal elements, or when you prefer the readability of unpacking.
  • Use itertools.chain when you only need to iterate once and want to avoid allocating a combined list.

In most production code, + and extend() cover the majority of cases. chain is a good fit for streaming data or when memory constraints are tight.

Common Mistakes and Edge Cases

One frequent mistake is assuming extend() returns the modified list. It returns None, so chaining calls like a.extend(b).extend(c) fails. Another issue is aliasing: if two variables reference the same list, extend() affects both, which can cause subtle bugs.

a = [1, 2] b = a a.extend([3]) print(b) # [1, 2, 3] # b sees the change

Using + would not affect b because it creates a new object. Also, be careful when concatenating a list with a string: [1, 2] + "ab" raises a TypeError because + requires both operands to be lists. extend() would add the characters individually if given a string, which is often unexpected.

a = [1, 2] a.extend("ab") print(a) # [1, 2, 'a', 'b']

If you need to append a string as a single element, wrap it in a list: a.extend(["ab"]).

Understanding these behaviors helps you avoid side effects and choose the concatenation method that matches the semantics your code requires.

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