Workshop · floor model for Survey of Software 1.040
Pure Python beats the standard library's C at keeping things sorted — by ten times at a hundred thousand items, by forty at a million. The race below starts when you say so. The question it is really asking is whether you needed them sorted all the time.
Everything runs in this tab. Pick a size, start the race, and watch the bars arrive in the order the structures finish.
Plain language. Switch to Engineering for versions, byte counts and the measurements behind each claim.
Four things people mean by "I need this sorted", and each has a different right answer. The numbers are for a hundred thousand random keys, measured for the survey on one machine; the race below re-measures them on yours.
100,000 random 40-bit integers, inserted one at a time, CPython 3.14.7, aarch64, single thread. Full sweep 1K–1M and the operations in S2 §0 of the survey.
sorted at every moment
61 ms
sortedcontainers SortedList — pure Python. The stdlib's C, bisect.insort, takes 553 ms.
sorted at the end
24 ms
list.append, then .sort() — 2.5× faster than keeping order as you go.
smallest-first only
6 ms
heapq — 10× faster than a sorted list, if the minimum is all you ever take.
never sorted
10 ms
dict — and membership is 10× faster than any sorted structure.
Use sortedcontainers when you need order between inserts — ranges, neighbours, rank, "what is just above this". Everything else has a cheaper stdlib answer, and the famous "pure Python beats C" is true of exactly that first case. One more thing the build found: the C extension it famously beat no longer ships for any current Python; what it still beats by ten times is the standard library's own C.
The rule of this page: every figure below is measured on your machine while you wait, by the Python libraries themselves running in this tab. Where a number came from research instead, it says so in orange.
Press start to download CPython 3.14 (WebAssembly). First time only, then cached.
Insert every key one at a time and keep the collection in order after each one. Each bar appears when its structure finishes; the length is the time it took.
Per-structure wall time for N single-key inserts (SortedList.add,
bisect.insort, list.append+sort, heapq.heappush,
dict.__setitem__), best-of under a budget for the fast ones, single run for the slow.
Multiples are against bisect.insort — the standard library's C answer to the same job.
The surprise is the order. The pure-Python library finishes before the standard library's C — and the gap widens with size, because a flat list must shift everything above each insert (that cost grows with the list), while SortedList keeps a list of short lists and shifts only one of them. Below a few thousand keys the flat list wins; above, it loses by more every decade.
SortedList is a list of lists with a load factor near 1,000 and a positional index over the
sublist lengths; add is a bisect into the right sublist and an insert that moves at
most ~1,000 pointers. bisect.insort on a flat list is one memmove of, on average,
half the list per insert — O(n) per insert, O(n²) for the race, and the memmove is fast enough that it wins
until about 5,000 keys. The survey's 2025 figure compared against bintrees' "C" red-black tree; measured
today (bintrees 2.2.0 on Python 3.14) that tree is pure Python — the compiled wheels only ever shipped for
3.6–3.9 — and it finishes in 506 ms natively, 8× behind SortedList.
Three things people do with a sorted collection, timed at the size you chose. Two of them have a standard-library answer that does not keep anything sorted at all.
10,000 membership probes; one range query; 10,000 pop-the-minimum operations — each against the cheapest alternative that gives the same answer.
One pipeline — group records by region, count users, sum bytes per user — written three ways: with toolz, with cytoolz, and with the standard library by hand.
groupby + frequencies(pluck(...)) + reduceby over
100,000 dicts; the by-hand version is defaultdict and Counter.
A persistent vector gives you a new value on every change without copying the old one. That is slow for building, and the whole point for updating.
1,000 single-element sets on a 100,000-element vector, each yielding a new
version, against copying a tuple each time; and 100,000 appends against
list.append. Note the implementation line — on Python 3.14 the C extension is absent.
From research, not measured in this tab
polars against pandas — both ship in Pyodide but the wheels are too heavy for a page that should load in seconds, so: native, one machine, 8 threads, 2M rows, polars 1.43 against pandas 3.0.5. Group-by with three aggregates 2.3–4× (two runs); filter + sum 9×; sort 2.6×. The survey's "5–100×" is from an older pandas; the range is real and the top of it is gone.
bintrees — development stopped in 2017, its README says "use sortedcontainers",
and its compiled trees only ever shipped for Python 3.6–3.9 on Mac and Windows. On any current interpreter
FastRBTree is the pure-Python RBTree: 506 ms for the 100K race natively, 8× behind
SortedList. 20K downloads a month, all of them inherited.
more-itertools — 348M downloads a month, no performance claim to test, and
none made: it is vocabulary (chunked, windowed, partition, unique_everseen)
that the standard library's itertools recipes section describes and does not ship. The least exotic thing
in the survey and the most installed.
Take what you just measured and hand it to whichever AI you use — the size you ran, the order the structures finished, and a brief for what to do next.
Clipboard envelope — no vendor deep link, no API key, no dependency on anyone's roadmap.
The measurement is one file, core.py, fetched by
this page and imported by the survey's bench. uvx marimo edit a notebook that imports it, or read it —
it is short.
This page is about staying sorted. Sorting once — which sort wins, on your data's shape and size — is sortie, the floor model for survey 1.001.
you want this sorted how? is a floor model for Survey of Software 1.040 — Advanced Collections Libraries, verified 2026-08-19. Libraries run under Pyodide (CPython 3.14 compiled to WebAssembly) in this tab; nothing is sent anywhere. Timings here are WebAssembly timings — roughly half native speed, same shape. Measurements are from your machine except where marked orange.
Made by Ivan Schneider · Model Citizen Developer