Skip to content

Memory Management and Garbage Collection

Beginning of the lesson

Memory Management and Garbage Collection

How CPython allocates, tracks and reclaims objects at runtime

Big Idea: CPython reclaims memory with two cooperating mechanisms. Reference counting frees an object the instant nothing points at it; the cyclic garbage collector exists only to clean up the one case reference counting cannot handle — groups of objects that reference each other but are unreachable from your code. Understanding where each mechanism starts and stops is what lets you reason about leaks, finalizer order and the memory profile of a long-running service.
By the end you will be able to:
  • explain how reference counting decides when an object is freed, and why sys.getrefcount always reads one high;
  • describe how reference cycles defeat pure refcounting and how the generational collector reclaims them;
  • use weakref to observe or cache an object without keeping it alive;
  • avoid the classic __del__ pitfalls (resurrection, undefined cycle order, shutdown);
  • diagnose unexpected memory growth with tracemalloc, gc and sys.getsizeof.

This topic assumes you already know Python's object model — identity, mutability and copy semantics from the previous topic. Here we go one level deeper, into the runtime machinery that keeps those objects alive.

1. Reference counting: the primary mechanism

Every object CPython creates carries a small integer field, ob_refcnt, that counts how many references currently point at it. Binding a name, appending to a list, passing an argument — each of these adds one to the count; deleting a name, rebinding it, or letting a scope exit subtracts one. The rule is exact and immediate: the moment the count reaches zero, the object is deallocated, its __del__ (if any) runs, and its own references are released — which may cascade and free still more objects.

import sys

a = [10, 20, 30]     # the list object now has one reference: 'a'
b = a                # a second reference to the SAME object
print(sys.getrefcount(a))   # 3  (a, b, and getrefcount's own argument)

del b                # one reference gone
print(sys.getrefcount(a))   # 2  (a, and the argument)

del a                # last real reference gone -> refcount hits 0 -> freed now

This is why CPython frees most memory deterministically and promptly, with no "stop the world" pause: reclamation is woven into ordinary reference bookkeeping. It is also why a file closed by a context manager is released right away when the with block ends and its last reference disappears.

Continue this lesson on YourAIZone

The full lesson has the complete explanation, worked examples, common mistakes, tests with instant checking and an AI tutor. Start right away as a guest, or create an account to keep your progress.

In the full lesson

  • The complete explanation
  • Worked examples
  • Common mistakes and how to avoid them
  • Test questions: 15
  • AI tutor that answers questions about the topic
  • Progress tracking for students, parents and teachers