skip to content

What is the difference between list.append() and list.extend() in Python?

level: juniorimportance: must knowfreq 76%

answer

  1. One argument, or one element per item
  2. What happens when you append a list
  3. extend accepts any iterable, strings included
  4. Both return None, never the list

basics

~10 s

list.append(x) adds x to the end as one single element, so appending a list nests it. list.extend(iterable) walks the argument and adds each of its items separately, growing the list by that many elements.

solid answer

~50 s

A Python list stores references to objects. `list.append(x)` stores one more reference to whatever `x` is, so `[1, 2].append([3, 4])` gives `[1, 2, [3, 4]]` — length 3, with a nested list in the last slot. `list.extend(it)` iterates `it` and appends each item it yields, so the same call with `extend` gives `[1, 2, 3, 4]` — length 4. `extend` accepts **any** iterable, not just a list, which is why `lst.extend("hi")` quietly adds the two characters `'h'` and `'i'`; that is a real bug source when a string arrives where a list of strings was expected. `lst += other` is `extend` under the hood and mutates in place; `lst = lst + other` builds a new list and demands another list on the right. Both methods mutate the list and return `None`, so `x = lst.append(1)` binds `None`, not the list.

code

pycon · 11 lines
pycon
>>> a = [1, 2]
>>> a.append([3, 4])
>>> a
[1, 2, [3, 4]]
>>> b = [1, 2]
>>> b.extend([3, 4])
>>> b
[1, 2, 3, 4]
>>> b.extend("hi")
>>> b
[1, 2, 3, 4, 'h', 'i']

go deeper

for a junior

Be ready to state the one-line difference and predict the exact result of appending a list versus extending with it, including the resulting length. Also remember that both methods return None.

for a middle

Explain the mechanics: a list stores references, extend consumes any iterable (strings and dicts included), and += routes through extend while + builds a new list and requires a list operand.

for a senior

Show the judgement: pick extend over an append loop for batches, and name the real-world bug where a string arrives instead of a list of strings and extend silently splices its characters into the data.

for a principal

Own the API-design angle: mutating methods returning None is a deliberate convention that separates in-place mutation from value-producing operations, and the append/extend split is where a schema mismatch in a data pipeline first shows up as an off-by-one nesting depth.

### What a list actually holds A CPython list is a sequence of pointers to objects. It never contains values “inline” — it contains references. Both `append` and `extend` add references to the end of that sequence; the only question is *how many* and *to what*. ### append: exactly one more element `list.append(obj)` stores one additional reference, to `obj` itself, whatever `obj` is. The list grows by exactly one, and `len(lst)` goes up by one, every time, regardless of the argument: ```python a = [1, 2] a.append([3, 4]) # one new element, which happens to be a list assert a == [1, 2, [3, 4]] assert len(a) == 3 assert a[2][0] == 3 ``` Note that `a[2]` **is** the same object that was passed in — no copy is made. Mutating `a[2]` afterwards is visible through both names. ### extend: one element per item of an iterable `list.extend(iterable)` iterates its argument and appends every item it yields. The list grows by however many items the iterable produced — zero for an empty one, `k` for a `k`-item sequence, and possibly a very large number for a generator: ```python b = [1, 2] b.extend([3, 4]) # two new elements assert b == [1, 2, 3, 4] b.extend(x * x for x in range(3)) assert b == [1, 2, 3, 4, 0, 1, 4] ``` Because the parameter is *any* iterable, the two classic surprises are strings and dicts. A `str` is an iterable of one-character strings, so `lst.extend("hi")` appends `'h'` and `'i'` rather than `"hi"`; a `dict` iterates its keys, so `lst.extend({"a": 1})` appends `"a"`. Neither raises — the data is simply wrong downstream. `append` has no such trap: it never looks inside its argument. ### The operators `lst += other` is the in-place form and is defined in terms of `extend`, so it accepts any iterable and mutates the existing object. `lst = lst + other` is different in two ways: it builds a brand-new list and rebinds the name, and `+` on a list requires another list on the right — `[1, 2] + "xy"` raises `TypeError`. Inside a function that received a list as an argument this distinction decides whether the caller sees the change: `+=` mutates the caller's list, `+` leaves it alone. ### Both return None Every mutating list method — `append`, `extend`, `insert`, `remove`, `reverse`, `clear` — returns `None` by deliberate design, to signal “I changed the object rather than producing a new one”. That rules out chaining: `lst.append(1).append(2)` fails with `AttributeError: 'NoneType' object has no attribute 'append'`, and `new = old.append(x)` binds `None` — an extremely common beginner bug. When you want a new list, use `old + [x]` or a comprehension. ### Cost, and which to reach for Both are cheap. `append` adds one reference; `extend` adds `k` references and costs O(k) — the work is proportional to what you are adding, not to what is already in the list. Between the two, the rule is a data-shape rule and not a performance rule: **append when you have one more item; extend when you have a batch of items.** Writing a `for` loop that calls `append` once per item of another sequence is not wrong, but `extend` expresses it in one call and does the whole loop inside the interpreter's C code, so it is meaningfully faster for large batches. One readability note: `extend` also documents intent. `results.extend(page)` says “splice this page of records into the running list”, while `results.append(page)` says “record this page as one unit”. Choosing the wrong one produces a list whose nesting depth is off by one, and the failure usually surfaces far away — in a consumer that iterates the result and gets lists where it expected records.

  • What does list.append() return, and why does that trip people up?
    It returns `None`. Python's mutating list methods deliberately return `None` to signal that they changed the object in place rather than producing a new one. So `new = old.append(x)` binds `None`, and `lst.append(1).append(2)` raises `AttributeError` on `None`. To get a new list instead, use `old + [x]` or a comprehension.
  • Is a loop of append calls or one extend call faster for adding a batch of items?
    `extend` is faster for anything but a handful of items. A Python-level loop pays interpreter overhead per iteration plus a method lookup and call each time, while `extend` does the whole loop inside the interpreter's C implementation and, for a sized argument, can grow the storage once instead of repeatedly. The difference grows with batch size.
  • What happens if you pass a dictionary to list.extend()?
    It appends the dictionary's keys, because iterating a `dict` yields keys. `[].extend({"a": 1, "b": 2})` gives `['a', 'b']` — no error, just data that is not what most people expect. If you want pairs, pass `d.items()`; if you want the dict itself as a single element, use `append`.

append puts a whole sealed box on the shelf; extend opens the box and shelves its contents one by one.

saying these in an interview costs you the question

  • Says append and extend are interchangeable
  • Thinks extend(x) adds x as one nested element
  • Expects lst.append(...) to return the modified list
  • Assumes extend only accepts a list, not any iterable
  • Thinks lst = lst + other mutates the original list

context