Python 3.15 and free-threading's second act
The GIL-free build is officially supported and the ecosystem work is the actual story. A progress report.
Python 3.15 is in beta. The headline features are incremental. The story worth tracking is the free-threading migration, which is now in the phase where it succeeds or stalls based on ecosystem work rather than on CPython.
where free-threading actually stands#
The GIL-free build has been officially supported since 3.14. The question was never whether CPython could do it — it demonstrably can — but whether the ecosystem would follow.
What has gone well:
The major numerical and data libraries did the work. NumPy, and the array and dataframe ecosystem around it, ship free-threaded wheels. That was the critical path, because those libraries are the reason a large fraction of Python's CPU-bound workload exists.
Single-threaded performance in the free-threaded build has improved substantially since 3.13. The gap against the default build has narrowed to something most applications would not notice.
Build infrastructure caught up. Producing free-threaded wheels is now a configuration change rather than a project.
What has gone slowly:
The long tail. Thousands of packages with C extensions have not been audited, and most of them will not be until someone hits a problem.
The subtler issue: pure-Python code that was accidentally thread-safe. The GIL made many operations effectively atomic. Code written under that assumption — a dictionary mutated from multiple threads, a counter incremented without a lock — worked by accident. Under free-threading it does not.
Nobody knows how much library code depends on this, because nobody ever had to think about it. It will be discovered one race condition at a time.
the practical guidance#
Do not switch production workloads casually. The performance win only exists for CPU-bound multithreaded work. If your workload is I/O-bound, asyncio was already handling it and free-threading gives you nothing.
Do test your libraries against it. The compatibility work has to happen and it happens when people run into problems and report them. If you maintain a package with a C extension, this is your work to do.
Do use it for the workloads it is for. Data processing, image and signal processing, simulation, anything numeric that currently uses multiprocessing and pays serialization and memory-duplication costs.
The migration from multiprocessing to threads for those workloads is frequently dramatic — not just faster, but simpler, because you delete the serialization layer.
the other thing in 3.15#
Subinterpreters continue maturing. concurrent.interpreters gives you isolated interpreter instances in one process, with much lower overhead than processes and much stronger isolation than threads.
This is the underrated option. For a workload where you want parallelism and do not want shared mutable state — which is most workloads — subinterpreters give you the process model's safety at closer to the thread model's cost.
It is the right default for a lot of the cases people currently reach for multiprocessing for, and it does not require any of the thread-safety auditing that free-threading does.
Error messages continue improving, which has been a multi-release trend and has made Python meaningfully friendlier for people learning it.
the assessment#
Python is executing a decade-long project to stop being single-threaded, and it is doing it without breaking the ecosystem, on an opt-in basis, with a fallback.
That is the right way to do it and it is slower than anyone wants. The alternative — a hard break, a Python 4 — would have been faster and would have cost the community what Python 3 cost it, which nobody has the appetite for.
Ask again in three years. The trajectory is good and the destination is not in doubt; only the timeline is.
— Dom, May 11, 2026