Skip to content
advanced Phase 18 · Python Performance

Memory Management

Understand Python memory model, garbage collection, and memory optimization.

1h
0 problems
Topic Progress 0%

Reference Counting and Object Model

Reference Counting and Object Model

CPython frees an object when its reference count hits zero. Counts rise with bindings, arguments, and container membership; they drop when names go away:

import sys
a = []            # count 1
b = a             # count 2
sys.getrefcount(a)  # reports 3 — the temporary argument counts too
del b             # back to 1; object still alive

Values are objects too: integers -5..256 are singletons, and identifier-like strings are interned, so a is b may be True for small integers and short identical identifiers — and False for larger ones created separately. Lists over-allocate capacity so append is amortized O(1), which is why sys.getsizeof exceeds len(items). Memory is a tree of references from stack frames and globals down to leaves.

The Cyclic Collector

The Cyclic Collector

Reference counting cannot free cycles (a.next = b; b.prev = a). A generational mark-and-sweep collector handles that:

import gc
gc.get_threshold()      # (700, 10, 10): gen0 threshold, gen1 increment, gen2 increment
gc.collect()            # force a full collection; returns unreachable count
gc.is_tracked(obj)      # containers are tracked; bare ints/floats usually are not
gc.freeze()             # promote import-time objects out of collection scope

Objects start in generation 0; surviving a collection promotes them (0→1→2), so long-lived structures are scanned rarely. del name removes a binding, not an object still referenced elsewhere. weakref registers without holding on — ideal for caches and parent back-pointers, since strong refs there create exactly the cycles the collector must chase.

Measuring and Reducing Memory

Measuring and Reducing Memory

Diagnose before optimizing:

import tracemalloc
tracemalloc.start()
base = tracemalloc.take_snapshot()
run_workload()
for stat in tracemalloc.take_snapshot().compare_to(base, "lineno")[:10]:
    print(stat)

Reduction levers, in order of payoff:

  • Algorithm: don’t hold the whole dataset — stream with generators
  • __slots__: drops the per-instance __dict__ (and __weakref__ unless requested) when instances number in the millions
  • Arrays over lists: array('f') or NumPy for numeric data — one machine value per element instead of pointer + PyObject header
  • Intern repeated strings: enum labels and constant keys reused across rows
  • WeakValueDictionary caches whose entries can be collected

Interview angle: "memory growth" is usually retention (an unexpected reference), not leakage — name the tool that proves it.

Quiz

1. Why can’t reference counting alone free a doubly linked list cycle?

Question 1 options

2. What does gc.get_threshold() returning (700, 10, 10) mean?

Question 2 options

3. Best first step when a service’s RSS climbs steadily?

Question 3 options

Flashcards

Question

Reference counting

Answer

CPython’s primary free mechanism; count reaches zero → deallocated. Cycles need the GC.

Question

Generational GC

Answer

Gen0/1/2 mark-and-sweep; survivors promote so old objects are scanned rarely.

Question

sys.getrefcount

Answer

Shows the count — one higher than expected because the call itself holds a reference.

Question

__slots__ benefit

Answer

Replaces per-instance __dict__ with fixed slots, cutting memory when instances are huge in number.

Revision Notes

Key Takeaways

  • 1. Reference counting frees acyclic objects instantly; the generational collector breaks cycles
  • 2. Small-int/string interning and list over-allocation explain surprising is/getsizeof results
  • 3. Measure with tracemalloc, then cut retention: streams, slots, arrays, weakrefs

Interview Tips

  • • Give both halves of CPython memory management — refcounts first, then the cyclic GC with generations
  • • Frame "leak" investigations as retention hunts and name the snapshot tool you would use

Cheat Sheet

refcount frees acyclic; gen GC (700,10,10) breaks cycles; small ints -5..256 interned; sys.getrefcount (+1); slots/weakref/tracemalloc to cut retention.

Continue Learning

Before You Start

  • No prerequisites listed.

Next Topics