CATEGORY 12 OF 13

OOP Design

Build classes with constructors, instance methods, encapsulation, and composition to model real-world data structures — the foundation for design-focused interview problems.

Time: Varies — depends on the data structure being implemented
Space: Varies — depends on what data is stored

Learn & Reference

Understanding the Pattern

OOP Design

Object-oriented programming (OOP) is a paradigm built around objects — bundles of data (fields) and behavior (methods) that operate on that data. In coding interviews, OOP surfaces whenever you need to design a data structure: stacks, queues, caches, event systems, and more. Understanding how to define a class, manage internal state, and expose a clean API is essential.

Classes and Constructors

A class is a blueprint for creating objects. The constructor runs once when an object is created, initializing internal state.

Key ideas:

  • Constructors accept parameters that configure the instance (e.g., capacity for an LRU cache)
  • Instance fields store the data each object owns
  • Every language has its own syntax, but the concept is universal

Instance Methods

Methods define what an object can do. They read and modify the instance's internal state.

Design guidelines:

  • Each method should have a single, clear responsibility
  • Method names should describe the action: push, get, isEmpty
  • Return meaningful values — return -1 or a sentinel when an operation fails (e.g., popping from an empty stack)

Encapsulation (Private vs Public)

Encapsulation means hiding internal details and exposing only a clean interface.

  • Public methods are the API users interact with
  • Private fields/methods are implementation details — they can change without breaking callers
  • In interviews, you rarely need strict access modifiers, but you should think about which methods are "internal helpers" vs the public interface

Composition vs Inheritance

  • Composition ("has-a"): An LRU cache has a hash map and a linked list. Prefer composition — it's flexible and easy to reason about
  • Inheritance ("is-a"): A MinStack is a stack with extra behavior. Use sparingly in interviews — it adds complexity

Most interview problems use composition: combine simple data structures (arrays, maps, lists) inside a class to build something more powerful.

When to Use OOP in Interviews

OOP is the right tool when:

  • The problem says "Design" or "Implement" a data structure
  • You need to maintain state across multiple method calls
  • Multiple operations share internal state (e.g., a stack's array is used by push, pop, and peek)
  • You want to encapsulate complexity behind a simple API

Common Mistakes

  1. Forgetting to initialize state in the constructor: Every field your methods use must be set up in the constructor — uninitialized fields cause runtime errors
  2. Not handling edge cases in methods: Always check for empty collections before popping, peeking, or removing — return a sentinel value like -1 instead of crashing
  3. Exposing internal data structures directly: Return copies or computed values, not references to internal arrays or maps that callers could mutate
  4. Making methods do too much: Each method should have one job — if a method is doing three things, split it up
  5. Confusing composition with inheritance: When combining data structures, store them as fields (composition) rather than trying to extend a base class
  6. Ignoring Go's struct-based OOP: Go doesn't have classes — use structs with pointer receiver methods and a Constructor function

When to Use

Design or Implement a data structure (stack, queue, cache, etc.)
Multiple operations that share and modify the same internal state
A need to encapsulate complexity behind a clean method interface
Problems requiring stateful objects across multiple method calls
Combining multiple simple data structures into one cohesive unit
Any problem where the input is a sequence of method calls on an object

Template

1# --- Basic Class Template ---
2class ClassName:
3 def __init__(self):
4 """Initialize internal state."""
5 self.data = []
6
7 def method_one(self, val):
8 """Add or modify data."""
9 self.data.append(val)
10
11 def method_two(self):
12 """Query or return data."""
13 if not self.data:
14 return -1
15 return self.data[-1]
16
17 def size(self):
18 """Return current size."""
19 return len(self.data)
{ }

Syntax Reference

1# === Class Definition ===
2class MyClass:
3 def __init__(self, value):
4 """Constructor — initialize instance state."""
5 self.value = value # public field
6 self._items = [] # "private" by convention
7
8 def get_value(self):
9 """Instance method — access self fields."""
10 return self.value
11
12 def add_item(self, item):
13 """Mutate internal state."""
14 self._items.append(item)
15
16 def size(self):
17 return len(self._items)
18
19# === Usage ===
20obj = MyClass(10)
21obj.add_item(42)
22obj.get_value() # 10
23obj.size() # 1
24
25# === Useful Built-ins for OOP ===
26isinstance(obj, MyClass) # type check
27hasattr(obj, 'value') # field check
28vars(obj) # instance dict
📋

Common Recipes

1# --- Stack via Class ---
2class Stack:
3 def __init__(self):
4 self.data = []
5 def push(self, val): self.data.append(val)
6 def pop(self): return self.data.pop() if self.data else -1
7 def peek(self): return self.data[-1] if self.data else -1
8 def is_empty(self): return len(self.data) == 0
9
10# --- Queue via Class ---
11class Queue:
12 def __init__(self):
13 self.data = []
14 def enqueue(self, val): self.data.append(val)
15 def dequeue(self): return self.data.pop(0) if self.data else -1
16 def front(self): return self.data[0] if self.data else -1
17 def is_empty(self): return len(self.data) == 0
18
19# --- Encapsulation Pattern ---
20class BankAccount:
21 def __init__(self, balance=0):
22 self._balance = balance # private by convention
23 def deposit(self, amount):
24 if amount > 0: self._balance += amount
25 def get_balance(self): return self._balance
26
27# --- Composition Pattern ---
28class MinStack:
29 def __init__(self):
30 self.stack = Stack() # compose with Stack
31 self.min_vals = Stack() # second stack for minimums
O(n)

Complexity

OperationTimeNotes
ConstructorO(1)Initialize fields and allocate storage
Field access / updateO(1)Direct read or write to instance variable
Method call overheadO(1)Method dispatch is constant time
Stack push / popO(1)Array-backed, amortized constant
Queue enqueue / dequeueO(1) amortizedO(n) if using array shift; O(1) with linked list or two-stack approach
Hash map get / putO(1) averageUsed inside many class-based designs
Linked list insert / deleteO(1)Given a node reference; O(n) to search

Watch Out

  • Forgetting to initialize state in the constructor: Every field your methods use must be set up in the constructor — uninitialized fields cause runtime errors
  • Not handling edge cases in methods: Always check for empty collections before popping, peeking, or removing — return a sentinel value like `-1` instead of crashing
  • Exposing internal data structures directly: Return copies or computed values, not references to internal arrays or maps that callers could mutate
  • Making methods do too much: Each method should have one job — if a method is doing three things, split it up
  • Confusing composition with inheritance: When combining data structures, store them as fields (composition) rather than trying to extend a base class
  • Ignoring Go's struct-based OOP: Go doesn't have classes — use structs with pointer receiver methods and a `Constructor` function