What is `weakref`, and when would you use it?

6 minadvancedmemoryweakrefcaching

Quick Answer

`weakref.ref(obj)` creates a reference to `obj` that **doesn't increment its refcount** — so it doesn't keep the object alive, and calling the weak reference returns `None` once the object is actually garbage collected. It's used to avoid reference cycles (e.g., parent/child back-pointers) and to build caches/registries that shouldn't themselves prevent an object from being freed when nothing else needs it.

Detailed Answer

Basic usage: a reference that doesn't keep the object alive

import weakref

class Resource:
    def __init__(self, name):
        self.name = name

r = Resource("db-connection")
weak = weakref.ref(r)

weak()          # <Resource object> -- still alive, call it to get the real object (or None)

del r            # the only STRONG reference is gone
weak()            # None -- the object was actually collected; weakref doesn't keep it alive

Unlike a normal reference (weak = r, which increments the refcount), weakref.ref(r) doesn't — so it has no say in whether r stays alive. Calling weak() returns the live object while it exists, or None once it's actually been collected.

Use case 1: caches that don't prevent eviction

import weakref

_cache = weakref.WeakValueDictionary()

def get_resource(key):
    resource = _cache.get(key)
    if resource is None:
        resource = load_expensive_resource(key)
        _cache[key] = resource
    return resource

WeakValueDictionary holds weak references to its values — entries are automatically removed once nothing else references the value. This gives you caching ("reuse the object if something else is still using it") without the cache itself becoming the reason large objects never get freed (a plain dict-based cache would keep every entry alive forever, a common source of memory leaks in long-running processes).

Use case 2: breaking reference cycles (parent/child back-pointers)

class TreeNode:
    def __init__(self, value):
        self.value = value
        self.children = []
        self._parent_ref = None

    @property
    def parent(self):
        return self._parent_ref() if self._parent_ref else None

    def add_child(self, child):
        child._parent_ref = weakref.ref(self)
        self.children.append(child)

The child's reference to its parent doesn't keep the parent alive — only the parent's children list (a strong reference, appropriately, since a parent should keep its children alive) does. This avoids creating a cycle at all, so refcounting alone can free the tree immediately once the root goes out of scope, rather than waiting for a periodic GC cycle pass.

Use case 3: observer patterns

class EventBus:
    def __init__(self):
        self._listeners = weakref.WeakSet()

    def subscribe(self, listener):
        self._listeners.add(listener)

Listeners registered with the bus don't have their lifetime extended just because they subscribed — if a listener object is otherwise no longer needed, it can still be garbage collected, and the WeakSet automatically drops the now-dead reference rather than leaking it indefinitely.

The limitation: not every object supports weak references

weakref.ref(42)   # TypeError: cannot create weak reference to 'int' object

Some built-in types (int, str, tuple in some cases) don't support weak references directly without wrapping — mainly relevant for custom classes (which support it by default unless __slots__ excludes __weakref__) and specific container types designed for this purpose (WeakValueDictionary, WeakKeyDictionary, WeakSet).

Interview-ready summary: weakref creates references that don't extend an object's lifetime, used for caches that shouldn't prevent eviction (WeakValueDictionary) and for breaking reference cycles in back-pointer relationships (parent/child, observer patterns) — letting reference counting reclaim memory immediately instead of relying on the periodic cyclic garbage collector.

Related Resources