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.