Python’s __slots__ is one of the most powerful memory optimization features in the language — and one of the least used. Most Python developers never encounter it outside of performance-critical libraries. That’s a mistake. Understanding __slots__ can cut your object memory usage by 40–60% and speed up attribute access by up to 35%.
⚡ TL;DR: By default, every Python object stores its attributes in a
__dict__dictionary.__slots__replaces that dict with a fixed-size array, eliminating the dictionary overhead entirely. For classes with millions of instances, this is transformative.
The Problem: Every Python Object Carries a Hidden Dictionary
Every Python class instance, by default, stores its attributes in a __dict__. That dictionary has significant overhead — a Python dict has a minimum size of ~200 bytes plus per-key overhead. When you create millions of objects, this adds up fast.
import sys
class PointWithDict:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
class PointWithSlots:
__slots__ = ['x', 'y', 'z']
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
p1 = PointWithDict(1.0, 2.0, 3.0)
p2 = PointWithSlots(1.0, 2.0, 3.0)
print(sys.getsizeof(p1)) # 48 bytes (object) + dict overhead
print(sys.getsizeof(p1.__dict__)) # 232 bytes — the hidden cost
print(sys.getsizeof(p2)) # 64 bytes — no __dict__ at all
# Total memory per instance:
# With __dict__: ~280 bytes
# With __slots__: 64 bytes
# Savings: 77%
Real Benchmark: 1 Million Objects
Deepen your Python knowledge
→ The Complete Python Bootcamp (Udemy) — 100 hours covering Python internals, OOP, and memory optimization.
Sponsored links. We may earn a commission at no extra cost to you.
import tracemalloc
import time
# Test with __dict__ (default)
tracemalloc.start()
points_dict = [PointWithDict(i, i*2, i*3) for i in range(1_000_000)]
current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f"With __dict__: {peak / 1024 / 1024:.1f} MB peak")
# Test with __slots__
tracemalloc.start()
points_slots = [PointWithSlots(i, i*2, i*3) for i in range(1_000_000)]
current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f"With __slots__: {peak / 1024 / 1024:.1f} MB peak")
# Typical output:
# With __dict__: 247.3 MB peak
# With __slots__: 91.6 MB peak
# Memory saved: 63%
Attribute Access Speed: The Hidden Bonus
import timeit
p_dict = PointWithDict(1.0, 2.0, 3.0)
p_slots = PointWithSlots(1.0, 2.0, 3.0)
# Benchmark attribute READ
dict_time = timeit.timeit(lambda: p_dict.x, number=10_000_000)
slots_time = timeit.timeit(lambda: p_slots.x, number=10_000_000)
print(f"__dict__ read: {dict_time:.3f}s")
print(f"__slots__ read: {slots_time:.3f}s")
print(f"Speedup: {dict_time/slots_time:.2f}x")
# Typical output:
# __dict__ read: 0.412s
# __slots__ read: 0.305s
# Speedup: 1.35x (35% faster attribute access)
When to Use __slots__ (and When Not To)
Use __slots__ when:
- Creating thousands or millions of instances of the same class
- Building data containers (Points, Vectors, Events, Records)
- Writing performance-critical library code
- The class has a fixed, known set of attributes
Don’t use __slots__ when:
- You need to dynamically add attributes at runtime
- Using multiple inheritance (complex interaction with slots)
- Working with pickle without explicitly defining
__getstate__/__setstate__ - The class has only a few instances — overhead not worth it
Advanced: __slots__ with Inheritance
class Animal:
__slots__ = ['name', 'age']
def __init__(self, name, age):
self.name = name
self.age = age
class Dog(Animal):
# MUST define __slots__ in subclass too
# Only add NEW attributes — inherited ones already covered
__slots__ = ['breed']
def __init__(self, name, age, breed):
super().__init__(name, age)
self.breed = breed
# If you DON'T define __slots__ in the subclass:
class BadDog(Animal):
pass # __dict__ comes back! Inherits Animal's slots but adds its own dict
d = Dog('Rex', 3, 'Lab')
# d.__dict__ doesn't exist — fully slot-optimized
b = BadDog('Fido', 2)
# b.__dict__ exists — memory savings lost
__slots__ in the Wild: Where Python Uses This Internally
Python’s own standard library uses __slots__ extensively for performance-critical classes. Check these yourself:
import datetime
print(datetime.datetime.__slots__)
# ('_hashcode', '_datetime') — datetime uses slots internally
from collections import OrderedDict
# CPython's internal linked list nodes use slots
# Popular libraries that rely on __slots__:
# - attrs (auto-generates __slots__ classes)
# - pydantic v2 (slots=True mode, ~40% faster)
# - numpy record arrays
# - SQLAlchemy column descriptors
The Modern Alternative: dataclasses with slots=True
from dataclasses import dataclass
# Python 3.10+ — slots=True on dataclasses
@dataclass(slots=True)
class Point:
x: float
y: float
z: float
# Gets you: __slots__ + __init__ + __repr__ + __eq__ automatically
# No boilerplate, full performance benefit
p = Point(1.0, 2.0, 3.0)
print(hasattr(p, '__dict__')) # False — slots active
print(p) # Point(x=1.0, y=2.0, z=3.0)
If you’re on Python 3.10+, use @dataclass(slots=True) — it’s the cleanest way to get slot optimization with zero boilerplate.
Quick Reference: The Slots Cheat Sheet
- ✅ Define
__slots__as a list or tuple of attribute name strings - ✅ Always define
__slots__in every class in an inheritance chain - ✅ Use
@dataclass(slots=True)on Python 3.10+ for clean syntax - ✅ Use
attrslibrary for slots + validation + serialization - ❌ Don’t mix
__dict__and__slots__(defeats the purpose) - ❌ Don’t use slots for classes that need dynamic attributes
Next Python deep dive: __del__ vs weakref — the memory leak pattern that bites every Python developer exactly once and is never forgotten.
Related reads on CheatCoders: The memory optimization mindset from __slots__ applies directly to V8’s hidden classes in JavaScript — both exploit type stability for performance gains. If you’re deploying Python services on AWS, the Lambda cold start guide covers Python package size optimization. External resource: Python official __slots__ documentation.
Recommended resources
- Fluent Python (2nd Edition) — Luciano Ramalho dedicates an entire chapter to __slots__, descriptors, and the data model. Essential reading for understanding why Python makes the memory trade-offs it does.
- Python Tricks: A Buffet of Awesome Python Features — Dan Bader covers the hidden features that make Python faster and cleaner. Great companion to this post.
Disclosure: This post contains affiliate links. If you purchase through these links, CheatCoders earns a small commission at no extra cost to you. We only recommend tools and books we genuinely find valuable.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.

Pingback: The V8 JIT Trick That Makes Your JS 3x Faster (Without Changing Logic) - CheatCoders
Pingback: Python Dataclasses: 10 Advanced Features That Make __init__ Obsolete - CheatCoders
Pingback: Python typing Module: Generics and Protocols That Catch Real Bugs at Compile Time - CheatCoders