OOP Design
Build classes with constructors, instance methods, encapsulation, and composition to model real-world data structures — the foundation for design-focused interview problems.
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
-1or 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
- 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
-1instead 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
Constructorfunction
When to Use
Template
1# --- Basic Class Template ---2class ClassName:3def __init__(self):4"""Initialize internal state."""5self.data = []67def method_one(self, val):8"""Add or modify data."""9self.data.append(val)1011def method_two(self):12"""Query or return data."""13if not self.data:14return -115return self.data[-1]1617def size(self):18"""Return current size."""19return len(self.data)
Syntax Reference
1# === Class Definition ===2class MyClass:3def __init__(self, value):4"""Constructor — initialize instance state."""5self.value = value # public field6self._items = [] # "private" by convention78def get_value(self):9"""Instance method — access self fields."""10return self.value1112def add_item(self, item):13"""Mutate internal state."""14self._items.append(item)1516def size(self):17return len(self._items)1819# === Usage ===20obj = MyClass(10)21obj.add_item(42)22obj.get_value() # 1023obj.size() # 12425# === Useful Built-ins for OOP ===26isinstance(obj, MyClass) # type check27hasattr(obj, 'value') # field check28vars(obj) # instance dict
Common Recipes
1# --- Stack via Class ---2class Stack:3def __init__(self):4self.data = []5def push(self, val): self.data.append(val)6def pop(self): return self.data.pop() if self.data else -17def peek(self): return self.data[-1] if self.data else -18def is_empty(self): return len(self.data) == 0910# --- Queue via Class ---11class Queue:12def __init__(self):13self.data = []14def enqueue(self, val): self.data.append(val)15def dequeue(self): return self.data.pop(0) if self.data else -116def front(self): return self.data[0] if self.data else -117def is_empty(self): return len(self.data) == 01819# --- Encapsulation Pattern ---20class BankAccount:21def __init__(self, balance=0):22self._balance = balance # private by convention23def deposit(self, amount):24if amount > 0: self._balance += amount25def get_balance(self): return self._balance2627# --- Composition Pattern ---28class MinStack:29def __init__(self):30self.stack = Stack() # compose with Stack31self.min_vals = Stack() # second stack for minimums
Complexity
| Operation | Time | Notes |
|---|---|---|
| Constructor | O(1) | Initialize fields and allocate storage |
| Field access / update | O(1) | Direct read or write to instance variable |
| Method call overhead | O(1) | Method dispatch is constant time |
| Stack push / pop | O(1) | Array-backed, amortized constant |
| Queue enqueue / dequeue | O(1) amortized | O(n) if using array shift; O(1) with linked list or two-stack approach |
| Hash map get / put | O(1) average | Used inside many class-based designs |
| Linked list insert / delete | O(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