append versus extend versus insert in Python comes down to one question: how many elements are you adding, and what are they? Get that right and the three methods stop being interchangeable; get it wrong and you end up with nested lists or a string split into letters.
What does append actually add?
append adds exactly one element to the end of the list. It does not look inside what you give it. Hand it a string and the string becomes a single element; hand it a list and the list becomes a single element, nested inside the outer list.
items = []
items.append("python")
print(items)
items.append(["data", "science"])
print(items)
print(len(items))
The last line is the diagnostic. When a list grows faster than you expect and every new entry is wrapped in brackets, you have appended a list where you meant to add its items. The fix is usually extend, or flattening the input before you append it.
How is extend different from append?
extend takes any iterable and adds each of its elements in turn. That makes it the opposite of append in both directions: one element becomes many, and a string becomes its individual characters.
letters = []
letters.append("ab")
print("append gave:", letters)
letters = []
letters.extend("ab")
print("extend gave:", letters)
numbers = [1, 2]
numbers.extend([3, 4, 5])
print(numbers)
Passing a string to extend is the mistake behind a whole family of bugs. If your data source is a comma-separated string and you meant to keep it intact, append is what you want; if you meant to split it into pieces, split it yourself with .split() and extend with the result. Silently relying on extend to split a string is only correct by accident.
Because extend accepts any iterable, you can feed it a tuple, a set or a generator. A set has no defined order, so if order matters, extend from a sorted list instead of straight from the set.
tags = ["python"]
tags.extend(("web", "data"))
print(tags)
tags.extend({"mobile", "cloud"})
print(sorted(tags))
A dictionary is an iterable too, and iterating one yields its keys. That makes extend(some_dict) a compact way to collect key names, though list(some_dict) says the same thing more clearly. Extending with some_dict.items() instead collects key-value pairs as tuples.
config = {"host": "localhost", "port": 5432}
keys = []
keys.extend(config)
print(keys)
pairs = []
pairs.extend(config.items())
print(pairs)
What does insert do?
insert places a single element at a position you choose and shifts everything from that position onwards one step to the right. The position is an index in the new list, so insert(0, x) puts the item at the front.
queue = ["b", "c"]
queue.insert(0, "a")
print(queue)
queue.insert(2, "c")
print(queue)
Two behaviours surprise people. Out-of-range positions do not raise; insert(100, x) puts the item at the end and insert(-100, x) puts it at the front, because Python clamps the position to the list's bounds. And the method returns None, so you cannot chain it.
Inserting at index zero repeatedly is the classic way to reverse a list by accident, because every insertion pushes the previous content along.
def reversed_by_insert(items):
out = []
for item in items:
out.insert(0, item)
return out
print(reversed_by_insert([1, 2, 3, 4]))
That works, and it works slowly. Inserting at the front has to move every existing element, so each call costs time proportional to the list length, while appending at the end is essentially free. For a list of a few dozen items the difference is invisible; for a list built in a loop over a large dataset, repeated insert(0, ...) turns a fast program into a slow one. Reach for reversed() or [::-1] when you want a reversed copy, and for append when you are filling a list in order.
What does += do to a list?
For a list, += is an alias for extend, not for append. This trips people up because on a string it also behaves like an extend, while on a tuple it creates a new tuple rather than modifying the original.
a = [1]
a += [2, 3]
print("list +=", a)
b = ("x",)
b += ("y",)
print("tuple +=", b)
c = ["p"]
c += "q"
print("string +=", c)
Because += on a list mutates it in place, it differs from a = a + b, which builds a new list and rebinds the name. For two large lists that is a meaningful difference in memory. Inside a function, though, a += b makes a a local variable for the whole function, so you would need a global declaration to change a module-level list that way. a.extend(b) avoids that trap entirely, because the name is only read.
How do you keep a list in sorted order while you add to it?
Sometimes the order of the list matters as you build it. Repeated insert keeps a list sorted but pays for the shifting, and sorting after every addition is worse. The standard library has the right tool: bisect.insort finds the position with a binary search and inserts there.
from bisect import insort
scores = [50, 70, 90]
insort(scores, 60)
insort(scores, 95)
print(scores)
That is a genuinely different tool from append: it inserts at the correct sorted position rather than at the end, and it does so without a linear scan for the right index. The trade-off is the same one you will meet throughout this module — keeping a list sorted on every insert costs more per insertion than appending and sorting once at the end.
Choosing a growth method
| You want to… | Use | Result |
|---|---|---|
| Add one known item to the end | append(item) | list grows by one |
| Add every item of another collection | extend(other) | list grows by the length of other |
| Put one item at a specific position | insert(pos, item) | list grows by one, later items shift right |
| Merge two lists into a new one | a + b | new list, originals untouched |
| Build a list from a generator | list(gen) | new list of the produced items |
As a working rule: reach for append by default, switch to extend the moment you find yourself writing append(list_of_things), and treat insert as a special case you reach for deliberately rather than by habit.
Try it yourself: start with an empty list and build the same result four ways — with append in a loop, with extend in a loop, with insert(0, ...) in a loop, and with a list comprehension over a range. Compare the four results and print them side by side. Then try the traps on purpose: append a list, extend a string, and insert at index 500, and write a one-line comment under each explaining what the method actually did.