Key Takeaways
- OOP organizes code around objects that combine data (attributes) and behavior (methods)
- The four pillars are encapsulation, abstraction, inheritance, and polymorphism
- Prefer composition over inheritance for more flexible and testable code
- Design patterns are named solutions to common OOP design problems
- Dunder methods in Python let you customize how objects behave with built-in operators
Object-oriented programming is the dominant paradigm in most production codebases. Python, JavaScript, Java, C++ — they all support OOP as a first-class paradigm. Understanding classes, inheritance, and design patterns isn't just about passing technical interviews. It's about being able to read and contribute to real codebases, which are almost universally organized around objects.
Classes and Objects: The Building Blocks
A class is a blueprint. An object is an instance of that blueprint. In Python:
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = balance # _ prefix = 'private' convention
def deposit(self, amount):
if amount > 0:
self._balance += amount
return self
def withdraw(self, amount):
if amount > self._balance:
raise ValueError('Insufficient funds')
self._balance -= amount
return self
@property
def balance(self):
return self._balance
def __repr__(self):
return f'BankAccount(owner={self.owner}, balance={self._balance})'
account = BankAccount('Alice', 1000)
account.deposit(500).withdraw(200) # method chaining
print(account.balance) # 1300
The Four Pillars of OOP
Encapsulation: Bundle data and methods together, hide internal state. Use properties and private attributes to control access. Abstraction: Expose only what callers need to know. Complex implementation details are hidden behind simple interfaces. Inheritance: A child class inherits attributes and methods from a parent class and can extend or override them. Polymorphism: Different classes can implement the same interface, and code can use them interchangeably. In Python:
class Shape:
def area(self):
raise NotImplementedError
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
class Rectangle(Shape):
def __init__(self, w, h):
self.w, self.h = w, h
def area(self):
return self.w * self.h
# Polymorphism: same call works for any shape
shapes = [Circle(5), Rectangle(4, 6)]
for s in shapes:
print(s.area())
Composition Over Inheritance: The Modern Preference
Deep inheritance hierarchies become fragile and hard to understand. Composition (an object contains other objects) is more flexible. The 'is-a' vs 'has-a' test: a Dog is-a Animal (inheritance makes sense). A Car has-a Engine (composition makes sense). Modern advice: favor composition, use inheritance when there's a genuine 'is-a' relationship, limit inheritance depth to 2-3 levels maximum.
# Composition example
class Logger:
def log(self, msg): print(f'[LOG] {msg}')
class EmailSender:
def send(self, to, msg): print(f'Sending to {to}: {msg}')
class UserService:
def __init__(self):
self.logger = Logger() # has-a Logger
self.emailer = EmailSender() # has-a EmailSender
def register(self, user):
self.logger.log(f'Registering {user}')
self.emailer.send(user.email, 'Welcome!')
Common Design Patterns You'll See in Real Codebases
Singleton: Ensure only one instance of a class exists (database connections, config managers). Factory: Create objects without specifying exact class — a factory function or class creates the right type based on parameters. Observer: Objects subscribe to events and get notified when they occur (event systems, UI updates). Decorator: Add behavior to objects without modifying their class — Python's @decorator syntax is this pattern. Strategy: Define a family of algorithms, encapsulate each one, make them interchangeable (sorting strategies, payment methods). You don't need to memorize all 23 Gang of Four patterns — learn these five and you'll recognize them everywhere.
Python Dunder Methods: Make Objects Behave Naturally
Dunder methods (double underscore) let your objects work with Python's built-in operations. Key ones to know: __init__ (constructor), __repr__ (developer-facing string representation), __str__ (user-facing string), __len__ (enables len(obj)), __getitem__ (enables obj[key] indexing), __iter__ and __next__ (makes objects iterable in for loops), __eq__ (enables == comparison), __lt__/__gt__ (enables < and > and sorting), __enter__/__exit__ (enables with statement). Python's data model is powerful — well-designed classes feel like built-in types.
OOP in JavaScript: Classes and Prototype
JavaScript added class syntax in ES6, but it's syntactic sugar over prototype-based inheritance. Classes work similarly to Python:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound.`;
}
}
class Dog extends Animal {
speak() {
return `${this.name} barks.`;
}
}
const d = new Dog('Rex');
console.log(d.speak()); // Rex barks.Key differences from Python: no private attributes natively (use # prefix for private fields in modern JS), this binding can be tricky (arrow functions capture this from surrounding scope), super() must be called in constructor before using this.
Frequently Asked Questions
- Should I learn OOP or functional programming?
- Learn both — they are complementary, not competing. OOP is dominant in most codebases for structuring systems. Functional programming is excellent for data processing and avoiding bugs from mutable state. Python and JavaScript support both, and good developers use each where it makes sense.
- What are design patterns and do I need to memorize them?
- Design patterns are reusable solutions to common software design problems, documented in the famous Gang of Four book. You don't need to memorize all 23. Learn the names and purposes of the most common ones (Singleton, Factory, Observer, Decorator, Strategy) and recognize them when you see them in code.
- When should I use classes vs functions in Python?
- Use classes when you have data and behavior that belong together and you need multiple instances with different state. Use functions when you're doing data transformation without maintaining state. Many Python developers prefer functional style for data science and scripts, OOP for larger applications and APIs.
- What is the difference between __str__ and __repr__ in Python?
- __repr__ is for developers — it should be a precise representation that could recreate the object. __str__ is for end users — a readable description. When you print an object, __str__ is called. When you type the object in the REPL, __repr__ is shown.
Ready to Level Up Your Skills?
Python programming, design patterns, and full-stack development — our bootcamp covers the skills that employers actually test for. 3 intensive days. Next cohorts October 2026 in 5 cities. Only $1,490.
View Bootcamp Details