Dataclasses & __slots__ Interview Questions & Answers
6 questions Updated 2026-06-18
Python interview questions on @dataclass — generated methods, frozen=True, field(default_factory=...) for mutable defaults, __slots__ for memory and speed, dataclass vs namedtuple vs NamedTuple, and __post_init__.
@dataclass auto-generates the boilerplate dunder methods from the
class's annotated fields — primarily __init__, __repr__, and
__eq__. You declare fields with type hints (and optional defaults) and
skip writing the constructor by hand.
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int = 0 # field with a default
p = Point(1, 2)
p # Point(x=1, y=2) — generated __repr__
p == Point(1, 2) # True — generated __eq__
# __init__(self, x, y=0) was generated automatically
You can opt into more (order=True for comparison operators, frozen=True for
immutability). It removes the repetitive plumbing while leaving normal methods
up to you.
@dataclass(frozen=True) makes instances immutable — assigning to a field
after creation raises FrozenInstanceError. As a bonus, frozen dataclasses
get a generated __hash__, so they're usable as dict keys and set
members.
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: int
y: int
p = Point(1, 2)
p.x = 9 # FrozenInstanceError — immutable
{p: "origin"} # hashable — works as a dict key
{Point(1, 2), Point(1, 2)} # one element — equal and hashable
Frozen dataclasses are the concise, modern way to define immutable value objects. Use them whenever an object represents a value that shouldn't change after creation.
A bare mutable default (tags: list = []) would be shared across all
instances — the same default-argument trap as in regular functions — so
dataclasses forbid it and raise ValueError. Instead, pass
field(default_factory=list), a zero-arg callable that creates a fresh
object per instance.
from dataclasses import dataclass, field
@dataclass
class Article:
title: str
tags: list = field(default_factory=list) # new list each instance
# tags: list = [] # would raise ValueError at class-definition time
a, b = Article("A"), Article("B")
a.tags.append("python")
b.tags # [] — independent, no shared state
Use default_factory for any mutable default (list, dict, set) or for a
value that must be computed at construction time. Plain immutable defaults
(numbers, strings, tuples) are fine inline.
__slots__ declares a fixed set of allowed attributes, so instances store
them in a compact array instead of a per-instance __dict__. This saves
significant memory (no dict per object) and gives faster attribute access.
The trade-off: you can't add new attributes not listed in slots.
class Point:
__slots__ = ("x", "y") # no per-instance __dict__
def __init__(self, x, y):
self.x, self.y = x, y
p = Point(1, 2)
p.x # 2 — fast access
p.z = 3 # AttributeError — 'z' not in __slots__
# p.__dict__ # also AttributeError — there is no dict
from dataclasses import dataclass
@dataclass(slots=True) # dataclasses can generate slots (3.10+)
class Fast:
x: int
y: int
Reach for __slots__ when you create huge numbers of small objects and
memory matters. For ordinary classes the flexibility of __dict__ is usually
worth more than the savings.
A namedtuple/typing.NamedTuple is an immutable tuple subclass —
lightweight, hashable, iterable, and index-accessible, but values can't change.
A @dataclass is a regular class — mutable by default, supports methods,
inheritance, and default_factory, but isn't a tuple (no unpacking/indexing
unless you add it).
from typing import NamedTuple
from dataclasses import dataclass
class PointNT(NamedTuple): # immutable, tuple-like
x: int
y: int
x, y = PointNT(1, 2) # unpacks like a tuple
@dataclass
class PointDC: # mutable, full class
x: int
y: int
def shift(self, dx): # can hold real methods
self.x += dx
PointNT(1, 2)[0] # 1 — index access (tuple)
PointDC(1, 2).shift(5) # mutate in place
Choose a NamedTuple for small immutable records and tuple semantics; choose a
dataclass when you need mutability, methods, or richer field control. Use
frozen=True dataclasses for immutable value objects that still want class
features.
__post_init__ runs automatically right after the generated __init__ —
it's the hook for validation and derived fields that depend on the
constructor arguments. It pairs with field(init=False) to declare attributes
that aren't constructor parameters but are computed afterward.
from dataclasses import dataclass, field
@dataclass
class Rectangle:
width: float
height: float
area: float = field(init=False) # not a constructor arg
# filled in after __init__:
def __post_init__(self):
if self.width <= 0 or self.height <= 0:
raise ValueError("dimensions must be positive") # validation
self.area = self.width * self.height # derived field
r = Rectangle(3, 4)
r.area # 12 — computed in __post_init__
Rectangle(-1, 4) # ValueError
Use __post_init__ whenever a dataclass needs logic the auto-generated
__init__ can't express — validation, normalization, or fields derived from
others.
Practice tests are coming soon
Get notified when interactive mock interviews and quizzes launch.