Garbage collection pauses in tests (gc.freeze)
Python's cyclic garbage collector rescans every long-lived object in a big test suite. Call gc.freeze() after collection and raise gc thresholds to cut GC pauses in pytest.
CPython frees most objects as soon as their reference count drops to zero. Objects that refer to each other in a cycle never get there, so a separate cyclic garbage collector runs from time to time, walks the objects that can form cycles, and frees the unreachable ones.
A test suite is a bad case for it. pytest has imported your whole application, every test module and every plugin before the first test runs: millions of functions, classes, dicts and model instances that live until the end of the session. Tests then allocate quickly (ORM objects, mocks, factories, request and response objects) and the collector keeps running, and its full collections walk all of those long-lived objects again, every time.
Why it's slow
The collector sorts objects into generations. New objects start in the youngest one; objects that survive a collection move to an older one. A collection starts when allocations minus deallocations since the last one exceed the first threshold, and older generations are collected less often. A full collection examines every tracked object, including everything imported at startup, and that's the expensive part: its cost grows with the size of the heap, not with the amount of garbage.
The fix: freeze what's already there
gc.freeze() moves every object the collector currently tracks into a permanent generation that later collections ignore. Call it once pytest has imported everything, and the collector only looks at objects created after that point:
# conftest.py
import gc
def pytest_collection_finish(session):
gc.collect()
gc.freeze()
pytest_collection_finish runs after all test modules are imported, so their objects are frozen too. Calling gc.collect() first frees the garbage left over from startup, so it isn't frozen along with the live objects. If you prefer pytest_sessionstart, it works too, but at that point the test modules haven't been imported yet. Under pytest-xdist each worker collects on its own and runs the hook itself.
Collect less often: gc.set_threshold
The first threshold decides how many net allocations trigger a young collection. Raising it means fewer, slightly larger collections:
# conftest.py
import gc
def pytest_collection_finish(session):
gc.collect()
gc.freeze()
gc.set_threshold(50_000, 50, 100)
Check your defaults with gc.get_threshold(): it's (700, 10, 10) on Python 3.12 and (2000, 10, 10) on recent 3.13 and 3.14 releases. Version notes:
- Python 3.14.0 to 3.14.4 shipped an incremental collector with only two generations. On those versions the second threshold sets how fast the old generation is scanned and the third is ignored.
- Python 3.14.5 reverted to the generational collector from 3.13, so thresholds mean the same as on 3.13 again.
Avoid gc.disable() for the whole session. It stops cyclic garbage from ever being freed, and on a large suite memory keeps growing until the run is slow for other reasons or the CI machine runs out.
Measure before and after
gc.callbacks calls your function at the start and end of every collection, which is enough to time them:
# conftest.py
import gc
import time
gc_seconds = 0.0
gc_started = 0.0
def track_gc(phase, info):
global gc_seconds, gc_started
if phase == "start":
gc_started = time.perf_counter()
else:
gc_seconds += time.perf_counter() - gc_started
gc.callbacks.append(track_gc)
def pytest_terminal_summary(terminalreporter):
terminalreporter.write_line(f"garbage collection: {gc_seconds:.2f}s")
Under pytest-xdist the summary runs in the controller process, so this only reports runs without -n. On Python 3.15, gc.get_stats() also has a duration key per generation.
Keep the tests correct
- This trades memory for speed. Frozen objects are still freed when their reference count drops to zero, but a frozen object that ends up in a reference cycle is never collected, and a higher threshold lets more garbage pile up between collections. Watch memory on large suites, especially in CI with several xdist workers on a small machine.
- Tests that call
gc.collect()to check that something is freed (weakref or finalizer tests) still work for objects they create: anything created after the freeze is collected as usual. - Keep it in
conftest.py. Don't copy test GC settings into production code without measuring there.
How flakytest finds it
flakytest puts samples taken while the garbage collector runs in the gc category. When garbage collection is at least a second and 3% of test time, it suggests The garbage collector is a noticeable share of test time, with the tests that paid the most. flakytest also measures each test's memory, so you can check what freezing and higher thresholds did to it in the same report.
Further reading
- Python:
gc.freeze() - Python:
gc.set_threshold() - Python:
gc.callbacks - What's new in Python 3.14: garbage collection
Related guides
- Fix slow pytest startup and heavy imports in tests: pytest takes seconds before the first test runs? Find slow imports with python -X importtime, set testpaths, trim conftest.py imports and disable plugins you don't use.
- How to profile a pytest suite: Profile slow pytest tests with cProfile, pytest-profiling, py-spy, pyinstrument, Python 3.15's tachyon or flakytest: the commands, what each one shows, and which to use when.
- How to speed up a slow pytest suite: A checklist for slow Python test suites, from the fixes that usually save the most time (password hashing, migrations, test data) to parallelism and CI. Each item links to a detailed guide.
Updated October 18, 2026 · Suggest a change