Plate 92
deque vs list: Queue FIFO Lab
Hands-on collections.deque vs list queue lab: real FIFO ops/s for append/popleft vs list.pop(0) at several prefill sizes on Linux localhost (eng lab).
Aditya Challa3 min read
Intro — what this post promises
Need a FIFO queue in Python? Folklore says “use collections.deque” because list.pop(0) is O(n). This lab times append + popleft vs append + pop(0) with a prefill of size S, plus drain and append-only baselines, on Linux localhost.
Related links:
- lru_cache hit vs miss localhost lab
- struct pack vs to_bytes localhost lab
- set vs list membership localhost lab
- sorted vs heapq vs bisect localhost lab
- copy vs deepcopy localhost lab
- string concat vs join localhost lab
- dataclass vs slots vs dict localhost lab
- logging vs print localhost lab
Lab honesty (1 Oct 2026 IST): Python 3.13.5. Affiliates: 0. FIFO cycle = one append + one front pop, keeping length ≈ S.
Verdict up front: at S=0–100 deque is only ~1.2–1.2× list. At S=1 000 already ~2.6×. At S=10 000 ~31× (18.6M vs 605k cycles/s). Draining 50k with pop(0) is ~255k/s vs deque ~31.6M.
Arms
| Arm | Pattern |
|---|---|
| FIFO | prefill S, then M× (append + pop front) |
| drain | prefill N, pop until empty |
| append-only | grow from empty (sanity) |
| list pop() end | drain with pop() — stack, not queue |
Lab topology
Script: lab-evidence/54-deque-vs-list-queue/results/run_lab.py.
Lead table — FIFO cycles/s (p50)
| Prefill S | deque | list pop(0) | deque÷list |
|---|---|---|---|
| 0 | 34,942,188 | 28,243,593 | 1.2× |
| 100 | 34,512,082 | 28,976,033 | 1.2× |
| 1,000 | 32,741,948 | 12,494,584 | 2.6× |
| 10,000 | 18,638,981 | 604,501 | 30.8× |
| 50,000 | 3,049,348 | 119,596 | 25.5× |
At S=10k, list pays ~1654 ns/cycle vs deque ~54 ns.
Drain and append baselines
| Arm | ops/s | ns/op |
|---|---|---|
| deque drain N=50k | 31,573,530 | 31.7 |
| list pop(0) drain N=50k | 255,487 | 3914.1 |
| list pop() end N=50k | 36,831,951 | 27.2 |
| list append N=100k | 41,751,387 | 24.0 |
| deque append N=100k | 36,859,592 | 27.1 |
list.pop() from the end matches deque drain speed — the bug is front removal, not “lists are slow.”
Pitfalls
- Benchmarking empty-queue FIFO only — S≈0 hides the O(n) shift.
- Using list as a queue “until it hurts” — at S=1k it already hurts on this box.
- Confusing stack (
pop()) with queue (pop(0)) — different asymptotics. - Indexing into a deque — O(n) for middle access; use a list if you need random access.
When to pick what
| Need | Prefer |
|---|---|
| FIFO / sliding window | collections.deque |
| Stack / LIFO | list + append/pop |
| Random access + occasional ends | list |
| Append-only buffer | either (append is peer) |
Reproduce
Evidence: /workspace/lab-evidence/54-deque-vs-list-queue/results/.
Closing
list.pop(0) does not stay fine. On this box deque FIFO is ~2.6× at S=1k and ~31× at S=10k. Drain 50k: deque ~32M/s vs list pop(0) ~255k. Use deque for queues; keep list for stacks and random access.
Lab evidence
What I found running this
Lab 1 Oct 2026 IST. Python 3.13.5. FIFO append+popleft/pop(0). S=0: deque 34.9M vs list 28.2M (~1.24x). S=1000: deque 32.7M vs list 12.5M (~2.6x). S=10000: deque 18.6M vs list 605k (~31x). S=50000: ~25x. Drain N=50k: deque 31.6M vs list pop(0) 255k. Affiliates: 0. Evidence: lab-evidence/54-deque-vs-list-queue/.
Related links
Plate 12
islice vs list Slice Windows: Localhost Lab
Hands-on itertools.islice vs list slice window lab: real ops/s taking ranges from sequences, measured on Linux localhost in this hands-on lab for SREs.
Observability & SRE · 1 Oct 2026
Plate 07
heapq.merge vs sorted(chain): Localhost Lab
Hands-on heapq.merge vs sorted(chain) multi-way merge: real records/s on pre-sorted lists, measured on Linux localhost today in this hands-on lab for SREs.
Observability & SRE · 1 Oct 2026
Plate 88
mmap Write vs pwrite Region: Localhost Lab
Hands-on mmap MAP_SHARED write+msync vs pwrite region update: real MB/s with durability labels, measured on Linux localhost in this hands-on lab for SREs.
Observability & SRE · 1 Oct 2026