Python Lists: Watching a Dynamic Array Grow, and Why insert(0) Is Slower
A Python list is a dynamic array: it backs onto a contiguous block of memory that has to be resized as the list grows. This post tracks a list’s actual byte size across 20 appends with sys.getsizeof, then times append() against insert(0, x) to see what adding at the front costs compared to adding at the end.
The code
import sys
import time
lst = []
prev_size = sys.getsizeof(lst)
print(f"{'len':>4} | {'bytes':>6} | {'grew?':>6}")
print(f"{len(lst):>4} | {prev_size:>6} | {'':>6}")
# Append one at a time and watch the underlying byte size.
# CPython over-allocates, so size only jumps on some appends, not every one.
for i in range(20):
lst.append(i)
size = sys.getsizeof(lst)
grew = "yes" if size != prev_size else ""
print(f"{len(lst):>4} | {size:>6} | {grew:>6}")
prev_size = size
# Now compare append() (adds at the end) against insert(0, x)
# (adds at the front, shifting every existing element over by one).
n = 20000
print(f"\ntiming {n} appends vs {n} insert(0, x) calls")
start = time.perf_counter()
appended = []
for i in range(n):
appended.append(i)
append_time = time.perf_counter() - start
start = time.perf_counter()
inserted = []
for i in range(n):
inserted.insert(0, i)
insert_time = time.perf_counter() - start
print(f"append(): {append_time:.6f}s")
print(f"insert(0, x): {insert_time:.6f}s")
First loop appends 20 items one at a time and prints sys.getsizeof(lst) after each; second part times n = 20000 calls to append() against n calls to insert(0, x) on separate empty lists.
Running it
Real output, Python 3.14.3:
len | bytes | grew?
0 | 56 |
1 | 88 | yes
2 | 88 |
3 | 88 |
4 | 88 |
5 | 120 | yes
6 | 120 |
7 | 120 |
8 | 120 |
9 | 184 | yes
10 | 184 |
11 | 184 |
12 | 184 |
13 | 184 |
14 | 184 |
15 | 184 |
16 | 184 |
17 | 248 | yes
18 | 248 |
19 | 248 |
20 | 248 |
timing 20000 appends vs 20000 insert(0, x) calls
append(): 0.001019s
insert(0, x): 0.023445s
The byte size didn’t grow on every append — it jumped 4 times (at lengths 1, 5, 9, and 17) and stayed flat the rest of the time, going from 56 bytes empty to 248 bytes at length 20. In the timing comparison, 20000 append() calls took 0.001019s while 20000 insert(0, x) calls took 0.023445s — about 23x longer for the same number of calls.
Takeaway
The real run showed two things: the list’s byte size grows in occasional jumps rather than on every single append, and adding at the front with insert(0, x) measured about 23x slower than append() for 20000 calls in this run.