Python 3.14.5 Reverts Incremental GC After Memory Pressure Reports
Python 3.14.0 (October 2025) changed the garbage collector from traditional generational collection to incremental collection. The goal: reduce maximum pause times by an order of magnitude or more for larger heaps. But users reported "memory pressure," and the Python team reverted the GC changes in 3.14.5 (June 2026).
The incremental GC was merged into Python's main branch in 2024 but removed from the 3.13 release branch. 3.14.0 was the first stable release with it. Now users who preferred the incremental collector can't use it at all—the change was not implemented as a runtime toggle (unlike Java or Go). The GC changes also bypassed the usual PEP process.
To understand the trade-offs, let's build both versions locally and examine memory management.
Building Both Versions
sudo apt-get update -y
sudo apt-get install -y build-essential pkg-config
git clone --depth 1 --branch v3.14.4 https://github.com/python/cpython cpython3.14.4
git clone --depth 1 --branch v3.14.5 https://github.com/python/cpython cpython3.14.5
(cd cpython3.14.4 && ./configure --with-trace-refs && make -j16)
(cd cpython3.14.5 && ./configure --with-trace-refs && make -j16)
The --with-trace-refs flag enables sys.getobjects() for debugging.
Reference Counting Primer
Python uses reference counting for immediate memory reclamation. Each object has a count of references; when it hits zero, the object is deallocated.
import sys
a = []
print(sys.getrefcount(a)) # 2: `a` + temp argument
b = a
print(sys.getrefcount(a)) # 3: `a`, `b`, temp
Reference counting works well until you hit circular references.
Circular References Leak Without GC
A cycle prevents reference counts from reaching zero:
import sys
class Obj: pass
a = Obj()
a.me = a # self-reference
i = id(a)
assert any(id(o) == i for o in sys.getobjects(0))
del a # still one reference from a.me
assert any(id(o) == i for o in sys.getobjects(0)) # still alive!
The object survives del because a.me still holds a reference. The garbage collector detects these cycles.
Generational GC (Traditional)
Python's traditional garbage collector (3.14.5 and earlier) uses three generations. Objects that survive collection are promoted. The collector runs less frequently for older generations. It collects entire generations at once, causing "stop-the-world" pauses.
Incremental GC (3.14.0–3.14.4)
The incremental collector reduces pauses by spreading work across multiple steps. It uses only two generations: young and old. When invoked, it collects the young generation and an increment of the old generation.
From the pull request: "maximum pause times are reduced by an order of magnitude or more for larger heaps."
Memory Pressure: The Downside
Users reported higher memory usage with the incremental collector. Why? The incremental collector may delay freeing objects, allowing memory to accumulate between increments. In contrast, the generational collector frees all unreachable objects in a single pass, releasing memory sooner.
Observing GC vs. Reference Counting
You can track which mechanism frees an object:
import gc, weakref
_in_gc = False
def _track(phase, info):
global _in_gc
_in_gc = phase == "start"
gc.callbacks.append(_track)
def watch(obj, label):
weakref.finalize(obj, lambda: print(
label, "freed by", "GC" if _in_gc else "refcount"))
class Obj(): pass
o = Obj()
watch(o, "o")
del o # freed by refcount
p = Obj()
p.me = p
watch(p, "p")
del p
gc.collect() # freed by GC
Output:
o freed by refcount
p freed by GC
What This Means for Developers
- If your application is sensitive to memory usage (e.g., long-running servers), stick with 3.14.5 or later.
- If you need low-latency pauses (e.g., real-time systems), the incremental collector was beneficial—but it's gone. You may need to tune GC thresholds or use manual
gc.collect(). - The revert highlights the difficulty of changing GC behavior without a toggle. The Python team chose to revert rather than risk memory regressions.
The Future
The GC changes were not put through a PEP, which may have contributed to the lack of a transition plan. A future PEP could reintroduce incremental GC with a runtime flag. For now, Python 3.14.5 is the recommended version for production.
Actionable Next Steps
- Upgrade to Python 3.14.5 if you're on 3.14.0–3.14.4.
- Profile memory usage with
tracemallocorobjgraphto detect cycles. - Consider using
weakrefto break cycles manually in critical paths. - Monitor GC pause times with
gc.get_stats()to tunegc.set_threshold().


