OOP for DSA

You do not need deep object-oriented programming (OOP) to solve algorithm problems, but you do need enough to build custom nodes, comparable items, and clean containers. This page covers the exact class features that show up in DSA: the constructor, the dunder methods that make your objects printable, comparable, and hashable, plus dataclasses. Read Python for DSA first if classes are new to you.

A minimal class with init#

A class is a blueprint. __init__ runs when you create an instance and sets up its attributes. self refers to the instance being built.

 1class Point:
 2    def __init__(self, x, y):
 3        self.x = x
 4        self.y = y
 5
 6    def distance_to_origin(self):
 7        return (self.x ** 2 + self.y ** 2) ** 0.5
 8
 9p = Point(3, 4)
10print(p.x, p.y)                 # 3 4
11print(p.distance_to_origin())   # 5.0

repr: make objects printable#

By default, printing an object shows an unhelpful memory address. Define __repr__ to return a clear string. This makes debugging your algorithms far easier.

 1class Point:
 2    def __init__(self, x, y):
 3        self.x = x
 4        self.y = y
 5
 6    def __repr__(self):
 7        return f"Point({self.x}, {self.y})"
 8
 9p = Point(3, 4)
10print(p)              # Point(3, 4)
11print([p, Point(1, 1)])   # [Point(3, 4), Point(1, 1)]

eq and hash: equality and hashing#

By default two objects are equal only if they are the same object. Define __eq__ to compare by value. If you make an object comparable by value and want to store it in a set or use it as a dict key, you also need __hash__, and it must agree with __eq__.

 1class Point:
 2    def __init__(self, x, y):
 3        self.x = x
 4        self.y = y
 5
 6    def __repr__(self):
 7        return f"Point({self.x}, {self.y})"
 8
 9    def __eq__(self, other):
10        return (self.x, self.y) == (other.x, other.y)
11
12    def __hash__(self):
13        return hash((self.x, self.y))
14
15print(Point(1, 2) == Point(1, 2))     # True, compared by value
16seen = {Point(1, 2), Point(1, 2)}
17print(len(seen))                      # 1, deduplicated by hash and eq

Rule of thumb: if a == b, then hash(a) == hash(b). Base both on the same tuple of fields and you are safe. Defining __eq__ without __hash__ makes the object unhashable, which is sometimes what you want for mutable objects.

lt: ordering for heaps and sorting#

Python’s sorted, min, max, and the heapq module compare items with the less-than operator. Define __lt__ to make your objects orderable. This is essential when you push custom objects onto a priority queue.

 1import heapq
 2
 3class Task:
 4    def __init__(self, priority, name):
 5        self.priority = priority
 6        self.name = name
 7
 8    def __lt__(self, other):
 9        return self.priority < other.priority
10
11    def __repr__(self):
12        return f"Task({self.priority}, {self.name!r})"
13
14heap = []
15heapq.heappush(heap, Task(3, "email"))
16heapq.heappush(heap, Task(1, "deploy"))
17heapq.heappush(heap, Task(2, "review"))
18
19while heap:
20    print(heapq.heappop(heap))   # smallest priority first
21# Task(1, 'deploy')
22# Task(2, 'review')
23# Task(3, 'email')

Without __lt__, heapq would raise a TypeError when two tasks tie and it tries to compare them.

dataclasses: less boilerplate#

Writing __init__, __repr__, and __eq__ by hand is tedious. The dataclasses module generates them for you from field declarations.

 1from dataclasses import dataclass, field
 2
 3@dataclass(order=True)
 4class Item:
 5    priority: int
 6    name: str = field(compare=False)
 7
 8a = Item(2, "b")
 9b = Item(2, "b")
10print(a)              # Item(priority=2, name='b')  auto __repr__
11print(a == b)         # True                         auto __eq__
12print(Item(1, "x") < Item(2, "y"))   # True          order=True adds __lt__

order=True generates comparison methods so instances work with sorted and heapq. Marking name with compare=False means only priority decides ordering, avoiding surprises when names differ. Use @dataclass(frozen=True) to make instances immutable and hashable, ideal for dict keys.

Building a linked-list Node#

Linked lists appear early in the 60-day curriculum. Each node holds a value and a reference to the next node.

 1class Node:
 2    def __init__(self, value, next=None):
 3        self.value = value
 4        self.next = next
 5
 6    def __repr__(self):
 7        return f"Node({self.value})"
 8
 9# Build 1 -> 2 -> 3
10head = Node(1, Node(2, Node(3)))
11
12# Traverse the list
13current = head
14while current is not None:
15    print(current.value)     # 1 2 3
16    current = current.next

That is the OOP core you need. Classes let you model nodes and comparable items, dunder methods make them print, compare, and hash correctly, and dataclasses cut the boilerplate. Take these into the algorithm lessons and keep the Big-O cheat sheet nearby as you reason about the cost of each operation.