Law of Demeter: Principle of Least Knowledge

Updated 2026-03-11

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.

Prerequisites: Understanding of classes and objects, method calls, basic encapsulation concepts, and familiarity with object composition. You should be comfortable reading code that instantiates objects and calls methods on them.

After this topic: Identify violations of the Law of Demeter in existing code, refactor coupled code to follow the principle of least knowledge, and design class interfaces that minimize dependencies between distant objects. You’ll be able to explain why method chaining creates tight coupling and propose better alternatives.

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:

  1. The object itself (self or this)
  2. Objects passed as parameters to the method
  3. Objects the method creates locally
  4. 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:

  • O itself
  • 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() or customer.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, provide user.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:

  1. Definition: “Only talk to immediate friends, not strangers”
  2. Why it matters: Reduces coupling, improves maintainability
  3. The four allowed calls: self, parameters, local objects, direct components
  4. 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:

  1. Identify the violation: “This creates tight coupling to the internal structure”
  2. Explain the problem: “If we change how X stores Y, all clients break”
  3. Propose solution: “Move the responsibility to the object that owns the data”
  4. Show code: Demonstrate the refactoring
  5. 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.