What is `__slots__`, and what problem does it solve?

6 minadvancedoopslotsperformancememory

Quick Answer

By default, every instance gets a `__dict__` to store arbitrary attributes, which costs memory per instance. Declaring `__slots__ = ("x", "y")` tells Python to allocate fixed storage for exactly those attribute names instead of a per-instance `dict`, cutting memory use significantly for classes with many instances, at the cost of losing dynamic attribute assignment (and needing extra care with multiple inheritance).

Detailed Answer

The default cost: a __dict__ per instance

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(1, 2)
p.__dict__          # {'x': 1, 'y': 2}
p.z = 3              # works -- __dict__ is dynamic, any attribute can be added

Every instance carries its own dict for attribute storage, which is flexible but has real memory overhead (~100+ bytes per instance just for the dict, on top of the attributes themselves) — significant if you create millions of small objects.

__slots__: fixed, dict-free storage

class Point:
    __slots__ = ("x", "y")

    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(1, 2)
p.z = 3              # AttributeError: 'Point' object has no attribute 'z'
p.__dict__            # AttributeError: no __dict__ at all

With __slots__, Python allocates fixed-size storage for exactly the named attributes (implemented as descriptors under the hood), eliminating the per-instance dict entirely. For a class with millions of instances (e.g., points in a large dataset, nodes in a graph), this can cut memory use by 40-50%+ and speed up attribute access slightly too.

The tradeoffs

  • No dynamic attributes — assigning an attribute not in __slots__ raises AttributeError. This is often a feature (catches typos early) but breaks code that relies on ad-hoc attribute assignment.
  • No default __dict__ or __weakref__ unless explicitly included in __slots__ — if you need weak references to instances, add "__weakref__" to the slots tuple.
  • Multiple inheritance is tricky — at most one base class in the MRO can have non-empty __slots__ with actual layout conflicts; combining multiple non-empty-__slots__ bases raises TypeError: multiple bases have instance lay-out conflict.
  • Every subclass needs its own __slots__ — if a subclass doesn't declare __slots__, it gets a __dict__ anyway, silently undoing the memory savings for that subclass's instances.

When to use it

Reach for __slots__ on classes you'll instantiate in large numbers (data records, tree/graph nodes, simple value objects) where memory footprint matters — not as a default for every class, since it removes flexibility for comparatively small classes where the memory savings don't matter.

Interview-ready summary: __slots__ trades away per-instance dict flexibility for fixed, lower-memory attribute storage — a meaningful win when instantiating a class millions of times, but something to opt into deliberately (and consistently across a whole inheritance chain), not a default.

Related Resources