CPython object model, allocation and reference counting
In CPython everything is a heap-allocated PyObject. Each object starts with a header containing ob_refcnt (reference count) and ob_type (pointer to its type object); variable-sized objects such as lists, tuples and ints embed a PyVarObject header with a size field. Names, container slots and the interpreter stack hold pointers to these objects; whenever one is stored or dropped the interpreter does Py_INCREF or Py_DECREF. When the count reaches zero, the object's deallocator runs immediately, which releases its memory and decrefs everything it references.
Allocation has layers. Objects of up to 512 bytes go through pymalloc, a specialized allocator that carves memory into arenas, pools and size-class blocks to avoid slow general-purpose malloc calls and fragmentation; larger objects fall back to the system allocator. Free lists exist for hot types (floats, tuples, lists). Important consequence: freeing an object returns memory to pymalloc's pools, and arenas are released to the OS only when completely empty, so a process's RSS often does not shrink after you delete a big structure.
Trade-offs: reference counting gives prompt, deterministic cleanup and incremental cost (no stop-the-world pauses), but adds overhead to every assignment, cannot free reference cycles on its own (that is what the cyclic GC is for), and the non-atomic counter updates are the main reason the GIL exists. Tracing collectors (PyPy, JVM) have higher throughput on allocation-heavy code but non-deterministic destruction. Version-dependent: 3.12 introduced immortal objects (PEP 683), whose refcounts are never touched; the 3.13 free-threaded build uses a different scheme (biased and deferred reference counting) to remove the GIL; allocator arena and pool sizes have also changed across versions, so I do not quote exact numbers.
Common mistakes: relying on refcount-driven destruction for resource cleanup (it is a CPython behaviour, not a language guarantee; use with blocks or contextlib), assuming sys.getsizeof reports total memory (it is shallow, excluding referenced objects), and putting heavy logic in del. For leak hunting I use tracemalloc, gc.get_referrers and objgraph rather than guessing.
0-2 years experience
2-5 years experience
5-8 years experience
8+ years experience