Workshop · floor model for Survey of Software 1.040

you want this sorted how?

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.

The whole answer, first

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.

The race

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.

Did you need it sorted?

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.

Run the race first.

cytoolz — the same functions, compiled

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.

Run the race first.

pyrsistent — what immutability costs, and what it buys

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.

Run the race first.

What could not be measured here

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 ; 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.

Start your build

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.

Run the race first.

Open the lab

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