List Performance

Deep dive · part of Python Lists

Lists are dynamic arrays: append and pop from the end are O(1), insert/pop(0) at the front are O(n). Use collections.deque when you need fast pushes/pops at both ends. Pre-allocate with [0]*n when size is known.

list is a dynamic array: append and pop at the end are amortized O(1); insert(0) and pop(0) shift all elements—O(n). Choosing deque for queue workloads avoids quadratic behavior on large n.

Preallocation with [0] * n or list comprehensions allocates once when size is known. bisect keeps sorted lists without resorting entire structures on each insert.

Production code combines this topic with logging, tests, and clear module boundaries so refactors stay safe when requirements grow.

collections.deque supports O(1) appendleft and popleft with maxlen cap.

list comprehension often beats map/filter for readability and speed in CPython.

Avoid repeated insert(0) in algorithms ported from linked-list pseudocode.

slice assignment can replace ranges in one operation.

array.array stores homogeneous numeric types compactly for millions of floats.

numpy arrays replace Python lists for numeric vector work at scale.

Memory: list of ints is heavier than array or numpy; profile RSS when storing millions of rows. For frequent membership tests on static data, convert to set once.

Copying: shallow copy with .copy() or [:]; deepcopy only for nested mutable structures.

Millions of ints to JSON may need array buffers or orjson—Python list of int objects multiplies memory.

orjson and array buffers beat list-of-int for serializing large numeric columns to JSON.

Read the parent tutorial on pythondeck.com for runnable snippets, then reproduce them locally in a virtual environment with pinned dependency versions matching your deployment target.

When pairing with teammates, agree on one idiomatic pattern per concern—mixed styles in one repo slow reviews and invite subtle integration bugs during merges.

Using list as FIFO with insert(0) on large data sets.

Building list with repeated append in tight loop when comprehension is clearer and fast enough.

Searching unsorted list in loop instead of set or bisect.

Holding references to huge lists longer than needed preventing GC.

Use deque for queues; list for stacks (append/pop).

Pre-size when length known from file line count or query COUNT.

Promote numeric hot paths to numpy after profiling.

Clear or del large lists in long-running services after processing batches.

Re-read the examples below with these ideas in mind; change variable names and inputs to match your own project.

The program below demonstrates deque vs list. Read the comments on each line, run the code, then change names or values to see how the output shifts.

# Example: deque vs list
# Run in the REPL or save as a .py file and execute with python.
import time
from collections import deque

N = 200_000

lst = []
t0 = time.perf_counter()
for i in range(N): lst.insert(0, i)
print("list.insert(0):", round(time.perf_counter()-t0, 3), "s")

dq = deque()
t0 = time.perf_counter()
for i in range(N): dq.appendleft(i)
print("deque.appendleft:", round(time.perf_counter()-t0, 3), "s")

This sample walks through pre-allocate in a small, runnable script. Paste it into the REPL or save it as a .py file before you continue to the next block.

# Example: Pre-allocate
# Run in the REPL or save as a .py file and execute with python.
n = 1_000_000
row = [0] * n        # O(n), one allocation
row[42] = 7
print(row[42], len(row))

Here is a hands-on illustration of bisect for sorted insert. Follow the inline comments first; only then execute the snippet and compare the result with what you expected.

# Example: bisect for sorted insert
# Run in the REPL or save as a .py file and execute with python.
import bisect
v = []
for x in [5, 1, 8, 3, 2, 9, 4]:
    bisect.insort(v, x)
print(v)

The program below demonstrates deque vs list. Read the comments on each line, run the code, then change names or values to see how the output shifts.

# list.insert(0) is O(n); deque.appendleft is O(1)
from collections import deque  # fast ends
import time  # timing
N = 20000  # scale
lst = []  # empty list
t0 = time.perf_counter()  # start
for i in range(N): lst.insert(0, i)  # slow front insert
print("list", round(time.perf_counter() - t0, 4))  # elapsed
dq = deque()  # empty deque
t0 = time.perf_counter()  # restart
for i in range(N): dq.appendleft(i)  # fast
print("deque", round(time.perf_counter() - t0, 4))  # faster

This sample walks through bisect insort in a small, runnable script. Paste it into the REPL or save it as a .py file before you continue to the next block.

# bisect keeps a list sorted on insert
import bisect  # binary search helpers
values = []  # sorted invariant
for x in [5, 1, 8, 3, 2, 9]:  # random order
    bisect.insort(values, x)  # insert at correct index
    print(values)  # show growth
print(values)  # fully sorted
print(values[0], values[-1])  # endpoints after sorting

« back to Python Lists All tutorials