PythonMastery

Take from the front of a deque, not a list

list.pop(0) shifts every remaining item along by one. deque.popleft() doesn't. Measure it on a 50,000-item queue, in the browser.

A list is fast at the end: append and pop() are cheap. Take from the front with pop(0) and every item behind it has to move up one place. For a queue, where you add at the back and take from the front, that turns into a lot of shuffling. collections.deque is built for both ends.

Measure it

python
import timeit
from collections import deque

def drain_list():
    queue = list(range(50_000))
    while queue:
        queue.pop(0)

def drain_deque():
    queue = deque(range(50_000))
    while queue:
        queue.popleft()

t_list = timeit.timeit(drain_list, number=1)
t_deque = timeit.timeit(drain_deque, number=1)
print(f"list.pop(0): {t_list * 1000:.0f} ms   deque.popleft(): {t_deque * 1000:.1f} ms")
print(f"the deque was about {t_list / t_deque:.0f}x faster")
example output · yours will differ
list.pop(0): 136 ms   deque.popleft(): 2.1 ms
the deque was about 65x faster

About 60–70× in desktop Python for 50,000 items, and about 14× when I ran it here in the browser: the exact number depends on where it runs, the gap does not go away. The list's time grows with the square of the queue length: twice the items means roughly four times the work. The deque's grows in a straight line.

The pattern in real code

A queue of jobs, processed in the order they arrived:

python
from collections import deque

jobs = deque(["resize photo.png", "email Ada", "backup notes"])
jobs.append("email Linus")          # new work goes on the back

while jobs:
    job = jobs.popleft()            # oldest first
    print("doing:", job)
output
doing: resize photo.png
doing: email Ada
doing: backup notes
doing: email Linus

A deque can also keep only the most recent items, which is handy for "the last N readings":

python
from collections import deque

last_three = deque(maxlen=3)
for temp in [21.4, 22.0, 23.1, 22.7, 21.9]:
    last_three.append(temp)
print(list(last_three))
output
[23.1, 22.7, 21.9]

Why it works

A list is one block of slots in order, so removing slot 0 means copying every slot after it. A deque is a chain of small blocks with a pointer at each end; taking from the front just moves the pointer.

When not to use it

If you mostly index into the middle (items[2500]) or slice, stay with a list: a deque is slower there. And for a handful of items pop(0) is fine. The problem only shows once the queue is long and busy.

Learn it properly: collections: The Stdlib's Hidden Power, Lists & Sequences