Law of Demeter: Principle of Least Knowledge
TL;DR
The Law of Demeter states that an object should only talk to its immediate friends, not strangers. This means calling methods only on objects you directly own, receive as parameters, or create locally—avoiding chains like obj.getA().getB().doSomething(). Following this principle reduces coupling and makes code more maintainable and testable.
Core Concept
What Is the Law of Demeter?
The Law of Demeter (LoD), also called the Principle of Least Knowledge, is a design guideline that limits which objects a method can interact with. The rule is simple: a method of an object should only call methods on:
- The object itself (
selforthis) - Objects passed as parameters to the method
- Objects the method creates locally
- Direct component objects (instance variables/fields)
You should NOT call methods on objects returned by other method calls. This prevents “train wrecks” like customer.getWallet().getMoney().getAmount().
Why It Matters
The Law of Demeter reduces coupling—the degree to which one class depends on the internal structure of another. When you chain method calls through multiple objects, you create dependencies on the entire chain. If any intermediate object changes its structure, your code breaks.
Benefits:
- Easier maintenance: Changes to internal structures don’t ripple through unrelated code
- Better encapsulation: Objects hide their internal structure
- Improved testability: Fewer dependencies mean easier mocking and testing
- Clearer responsibilities: Each object manages its own behavior
The “Only One Dot” Rule
A simplified heuristic is: use only one dot per statement (excluding the object itself). While not absolute, object.method() is usually fine, but object.method().anotherMethod() often violates LoD.
Exception: Fluent interfaces and builder patterns intentionally chain methods on the same object (builder.setName().setAge().build()), which is acceptable because you’re talking to the same “friend” throughout.
Formal Definition
For a method M of object O, M should only invoke methods of:
Oitself- Parameters of
M - Any objects created within
M - Direct components of
O(instance variables) - Global variables accessible by
O(use sparingly)
Visual Guide
Law of Demeter Violation vs. Compliance
graph LR
A[Client] -->|violates LoD| B[Customer]
B -->|getWallet| C[Wallet]
C -->|getMoney| D[Money]
D -->|getAmount| E[Amount]
style A fill:#ffcccc
style E fill:#ffcccc
A2[Client] -->|complies with LoD| B2[Customer]
B2[Customer] -.->|internal| C2[Wallet]
C2 -.->|internal| D2[Money]
style A2 fill:#ccffcc
style B2 fill:#ccffcc
Top: Client reaches through multiple objects (violation). Bottom: Client asks Customer directly, Customer handles internal navigation (compliant).
Allowed Method Calls Under Law of Demeter
graph TD
M[Method M in Object O]
M -->|1. Call methods on self| O[Object O itself]
M -->|2. Call methods on| P[Parameters]
M -->|3. Call methods on| L[Locally created objects]
M -->|4. Call methods on| C[Direct components/fields]
style M fill:#e1f5ff
style O fill:#d4edda
style P fill:#d4edda
style L fill:#d4edda
style C fill:#d4edda
The four categories of objects a method can safely call methods on without violating the Law of Demeter.
Examples
Example 1: Violation and Fix - Customer Payment
Violating the Law of Demeter
class Money:
def __init__(self, amount):
self.amount = amount
def get_amount(self):
return self.amount
class Wallet:
def __init__(self, money):
self.money = money
def get_money(self):
return self.money
class Customer:
def __init__(self, wallet):
self.wallet = wallet
def get_wallet(self):
return self.wallet
# Client code - VIOLATION
def process_payment(customer, price):
# Reaching through multiple objects - "train wreck"
available = customer.get_wallet().get_money().get_amount()
if available >= price:
print(f"Payment of ${price} approved")
else:
print(f"Insufficient funds: ${available} < ${price}")
# Usage
money = Money(100)
wallet = Wallet(money)
customer = Customer(wallet)
process_payment(customer, 50) # Output: Payment of $50 approved
Problem: The client knows too much about Customer’s internal structure (Customer has Wallet, Wallet has Money, Money has amount). If we change how Customer stores money, all client code breaks.
Following the Law of Demeter
class Money:
def __init__(self, amount):
self._amount = amount # Private
def is_sufficient(self, required):
return self._amount >= required
def subtract(self, amount):
if self.is_sufficient(amount):
self._amount -= amount
return True
return False
class Wallet:
def __init__(self, money):
self._money = money # Private
def can_afford(self, price):
return self._money.is_sufficient(price)
def deduct(self, price):
return self._money.subtract(price)
class Customer:
def __init__(self, wallet):
self._wallet = wallet # Private
def make_payment(self, price):
"""Customer handles its own payment logic"""
if self._wallet.can_afford(price):
if self._wallet.deduct(price):
return True
return False
# Client code - COMPLIANT
def process_payment(customer, price):
# Only talk to customer, not its internal objects
if customer.make_payment(price):
print(f"Payment of ${price} approved")
else:
print(f"Payment of ${price} declined")
# Usage
money = Money(100)
wallet = Wallet(money)
customer = Customer(wallet)
process_payment(customer, 50) # Output: Payment of $50 approved
process_payment(customer, 60) # Output: Payment of $60 declined
Solution: Each object manages its own data. The client only talks to Customer, Customer talks to Wallet, Wallet talks to Money. Changes to internal structure don’t affect the client.
Try it yourself: Add a CreditCard class as an alternative payment method in Wallet. Notice how the client code doesn’t need to change.
Example 2: Document Formatting - Avoiding Deep Navigation
Violating the Law of Demeter
class Font:
def __init__(self, size):
self.size = size
def get_size(self):
return self.size
class Paragraph:
def __init__(self, font):
self.font = font
def get_font(self):
return self.font
class Document:
def __init__(self, paragraphs):
self.paragraphs = paragraphs
def get_paragraphs(self):
return self.paragraphs
# Client code - VIOLATION
def calculate_total_font_size(document):
total = 0
# Reaching deep into document structure
for paragraph in document.get_paragraphs():
total += paragraph.get_font().get_size()
return total
# Usage
font1 = Font(12)
font2 = Font(14)
para1 = Paragraph(font1)
para2 = Paragraph(font2)
doc = Document([para1, para2])
print(calculate_total_font_size(doc)) # Output: 26
Problem: Client code navigates through Document → Paragraph → Font. If we change how fonts are stored, client code breaks.
Following the Law of Demeter
class Font:
def __init__(self, size):
self._size = size
def get_size(self):
return self._size
class Paragraph:
def __init__(self, font):
self._font = font
def get_font_size(self):
"""Paragraph knows how to get its own font size"""
return self._font.get_size()
class Document:
def __init__(self, paragraphs):
self._paragraphs = paragraphs
def calculate_total_font_size(self):
"""Document calculates its own metrics"""
return sum(p.get_font_size() for p in self._paragraphs)
def get_paragraph_count(self):
return len(self._paragraphs)
# Client code - COMPLIANT
def display_document_stats(document):
# Only talk to document
total_size = document.calculate_total_font_size()
count = document.get_paragraph_count()
print(f"Total font size: {total_size}, Paragraphs: {count}")
# Usage
font1 = Font(12)
font2 = Font(14)
para1 = Paragraph(font1)
para2 = Paragraph(font2)
doc = Document([para1, para2])
display_document_stats(doc) # Output: Total font size: 26, Paragraphs: 2
Solution: Each object provides high-level operations. Document calculates its own statistics, Paragraph provides its font size. The client doesn’t navigate the internal structure.
Java/C++ Note: In Java, you’d use private fields with getter methods. In C++, use private members with public accessor methods. The principle remains the same: avoid exposing internal structure that allows deep navigation.
Try it yourself: Add a method to Document that returns the average font size. Implement it without violating LoD.
Common Mistakes
1. Confusing LoD with “Never Use Getters”
Mistake: Thinking the Law of Demeter means you can never use getter methods.
# This is FINE - talking to a direct component
class Order:
def __init__(self, customer):
self._customer = customer
def get_customer_name(self):
return self._customer.get_name() # OK: customer is a direct component
Explanation: Getters are fine when used on direct components (instance variables). The problem is chaining getters to reach distant objects. order.get_customer().get_name() from outside Order is a violation, but Order internally calling self._customer.get_name() is not.
2. Violating LoD with Data Structures
Mistake: Treating data structures (like lists, dictionaries) as if LoD doesn’t apply.
# VIOLATION - even with built-in types
class ShoppingCart:
def __init__(self):
self.items = [] # Public list
# Client code
cart = ShoppingCart()
cart.items.append(item) # Reaching into cart's internals
total = sum(i.get_price() for i in cart.items) # Navigating internal structure
Better approach:
class ShoppingCart:
def __init__(self):
self._items = [] # Private
def add_item(self, item):
self._items.append(item)
def get_total(self):
return sum(i.get_price() for i in self._items)
# Client code
cart = ShoppingCart()
cart.add_item(item) # Talk to cart, not its internals
total = cart.get_total()
Explanation: Even collections should be encapsulated. Provide methods that operate on the collection rather than exposing it directly.
3. Over-Engineering with Wrapper Methods
Mistake: Creating excessive wrapper methods that just delegate without adding value.
# OVER-ENGINEERED
class Customer:
def __init__(self, address):
self._address = address
def get_street(self):
return self._address.get_street()
def get_city(self):
return self._address.get_city()
def get_zip(self):
return self._address.get_zip()
# ... wrapping every address method
Better approach: Provide high-level operations instead of wrapping every detail.
class Customer:
def __init__(self, address):
self._address = address
def get_formatted_address(self):
return self._address.format() # High-level operation
def is_in_delivery_zone(self, zone):
return self._address.is_in_zone(zone) # Meaningful operation
Explanation: Don’t blindly wrap every method. Instead, think about what operations make sense at each level of abstraction.
4. Ignoring LoD for Fluent Interfaces
Mistake: Thinking fluent interfaces (method chaining on the same object) violate LoD.
# This is FINE - fluent interface, same object throughout
query = QueryBuilder()
query.select('name').from_table('users').where('age > 18').order_by('name')
Explanation: Fluent interfaces return self (or the same object type), so you’re always talking to the same “friend.” This doesn’t violate LoD because you’re not reaching through to different objects.
5. Applying LoD Too Strictly to Domain Logic
Mistake: Forcing LoD compliance when it obscures clear domain relationships.
# Sometimes this is clearer for simple data objects
address_string = f"{person.address.street}, {person.address.city}"
# vs. over-engineered
address_string = person.get_formatted_address() # Might be overkill for DTOs
Explanation: For simple Data Transfer Objects (DTOs) or value objects with public fields and no behavior, strict LoD compliance can add unnecessary complexity. Use judgment: if the object is purely data with no logic, direct field access might be acceptable. But for objects with behavior and invariants, follow LoD.
Interview Tips
How Interviewers Test Law of Demeter
1. Code Review Questions: You’ll be shown code with method chaining and asked to identify problems.
- Example: “What’s wrong with
order.getCustomer().getAddress().getZipCode()?” - Answer: Explain the coupling issue, then refactor to
order.getCustomerZipCode()orcustomer.getZipCode()depending on context.
2. Design Questions: You’ll be asked to design a system and they’ll watch for LoD violations.
- Example: “Design a library system where users can borrow books.”
- Watch out: Don’t write
user.getAccount().getBooks().get(0).getTitle(). Instead, provideuser.getBorrowedBookTitles()or similar high-level methods.
3. Refactoring Exercises: You’ll be given poorly designed code and asked to improve it.
- Strategy: Look for “train wrecks” (multiple dots). Identify which object should own the responsibility. Move logic closer to the data.
Key Points to Mention
When explaining LoD, cover these points:
- Definition: “Only talk to immediate friends, not strangers”
- Why it matters: Reduces coupling, improves maintainability
- The four allowed calls: self, parameters, local objects, direct components
- Trade-offs: Sometimes creates more methods, but the decoupling is worth it
Common Interview Scenario
Interviewer: “How would you handle this situation where you need data from a nested object?”
Strong answer structure:
- Identify the violation: “This creates tight coupling to the internal structure”
- Explain the problem: “If we change how X stores Y, all clients break”
- Propose solution: “Move the responsibility to the object that owns the data”
- Show code: Demonstrate the refactoring
- Discuss trade-offs: “We add a method to X, but we decouple clients from X’s internals”
Red Flags to Avoid
- Don’t say: “We should never use getters” (too extreme)
- Don’t say: “LoD is just about counting dots” (oversimplification)
- Don’t: Blindly apply LoD to every situation without considering context
Bonus Points
- Mention the Tell, Don’t Ask principle as related concept
- Discuss how LoD relates to encapsulation and information hiding
- Note that LoD is a guideline, not an absolute rule—use professional judgment
- Connect to testing: “Following LoD makes mocking easier because we have fewer dependencies”
Key Takeaways
-
The Law of Demeter limits method calls to four categories: the object itself, method parameters, locally created objects, and direct component objects—avoiding chains through multiple objects.
-
“Train wrecks” like
obj.getA().getB().getC()create tight coupling to internal structures, making code fragile and hard to maintain when those structures change. -
Push responsibility down to the objects that own the data rather than letting clients navigate through object graphs—each object should manage its own internals.
-
Fluent interfaces and builder patterns are exceptions because they chain methods on the same object, not reaching through to different objects.
-
Balance LoD with pragmatism: for simple data transfer objects or value objects, strict compliance might add unnecessary complexity—apply the principle where it provides clear benefits in reducing coupling.