đź§ How Does Python's Memory Management Work?


When we write Python code, we rarely stop to think about what happens under the hood. Variables, lists, dictionaries… everything “just works.” But what ensures that memory is used efficiently? How does Python know when it’s safe to free memory?
In this article — the seventh in my Python fundamentals series — we’ll explore how Python manages memory, including allocation, reference counting, garbage collection, and best practices to avoid memory leaks.
đź§± Key Concept: Everything is an Object
In Python, everything is an object: numbers, strings, functions, classes… Every object is stored in memory and managed by the interpreter.
Each object in memory holds:
Its type (e.g.,
int
,str
,list
)Its value (stored data)
A reference count
Other internal metadata
đź§® Reference Counting
At the core of Python’s memory management is reference counting. Every time you assign an object to a variable, its reference count increases.
When the reference is removed (e.g., a variable goes out of scope or is reassigned), the count decreases. When the count reaches zero, Python automatically deletes the object and reclaims the memory.
Example:
import sys
a = [1, 2, 3]
print(sys.getrefcount(a)) # Output: ~2 (one from 'a' and one from the function argument)
b = a
print(sys.getrefcount(a)) # Now ~3
del b
print(sys.getrefcount(a)) # Back to ~2
♻️ Garbage Collector
Reference counting alone can’t handle all cases — especially cyclic references.
Example of a cycle:
class Node:
def __init__(self):
self.ref = None
a = Node()
b = Node()
a.ref = b
b.ref = a
Even if a
and b
are deleted from the outer scope, they still reference each other. Their reference count never reaches zero.
That’s why Python includes a garbage collector that runs periodically to detect and clean up cycles.
You can inspect or manually trigger it using the gc
module:
import gc
print(gc.isenabled()) # True
gc.collect() # Manually trigger collection
📊 Generational Garbage Collection
To improve performance, Python categorizes objects into generations:
Generation 0: newly created objects
Generation 1: objects that survived one collection
Generation 2: long-lived objects
Older generations are collected less frequently, as they tend to be more stable and costly to inspect.
đź§Ľ Best Practices for Managing Memory
Even though Python does most of the work, following good practices can help avoid leaks and boost performance:
1. Avoid circular references
Prefer structures that don’t form cycles, or use weakref
when needed.
import weakref
class Data:
pass
d = Data()
r = weakref.ref(d)
print(r()) # Access the object
2. Explicitly clean up resources
When working with files, connections, or large objects, use context managers:
with open('file.txt') as f:
data = f.read()
3. Watch out for global structures or unmanaged caches
Global variables that accumulate data can cause silent memory leaks over time.
4. Use memory profiling tools
Modules like tracemalloc
, objgraph
, or memory_profiler
help you visualize memory usage and detect trouble spots.
import tracemalloc
tracemalloc.start()
# Code to measure
...
snapshot = tracemalloc.take_snapshot()
for stat in snapshot.statistics('lineno')[:5]:
print(stat)
📌 Conclusion
Python’s memory management combines:
✅ Reference counting – to free memory as soon as objects are no longer needed
✅ Garbage collection – to handle cyclic references
✅ Generational strategy – to balance performance and efficiency
Understanding these mechanisms helps you write cleaner code, avoid memory leaks, and optimize your applications mindfully.
If you’re enjoying this Python fundamentals series, feel free to share it or leave feedback!
Next up: we'll explore dynamic and strong typing in Python — how does it really work in practice?
#Python #MemoryManagement #GarbageCollector #PythonTips #DevLife #CleanCode #PythonFundamentals
Subscribe to my newsletter
Read articles from Bruno Marques directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
