ConstructiCat Logo
CodeBust.
Browse section ▾

Circular Dependency.

A circular dependency is an antipattern where two or more modules depend on each other, so none of them can be built, tested or reused alone.

In software engineering, a circular dependency (or cyclic dependency) is a relation between two or more modules which either directly or indirectly depend on each other to function properly. Such modules are also known as mutually recursive.

Overview

Circular dependencies are natural in many domain models where certain objects of the same domain depend on each other. However, in software design, circular dependencies between larger software modules are considered an anti-pattern because of their negative effects. Despite this, such circular (or cyclic) dependencies have been found to be widespread among the source files of real-world software. Mutually recursive modules are, however, somewhat common in functional programming, where inductive and recursive definitions are often encouraged.

Problems

Circular dependencies can cause many unwanted effects in software programs. Most problematic from a software design point of view is the tight coupling of the mutually dependent modules which reduces or makes impossible the separate re-use of a single module.

Circular dependencies can cause a domino effect when a small local change in one module spreads into other modules and has unwanted global effects (program errors, compile errors). Circular dependencies can also result in infinite recursions or other unexpected failures.

Circular dependencies may also cause memory leaks by preventing certain automatic garbage collectors (those that use reference counting) from deallocating unused objects.

Causes and solutions

In very large software designs, software engineers may lose the context and inadvertently introduce circular dependencies. There are tools to analyze software and find unwanted circular dependencies.

Circular dependencies can be introduced when implementing callback functionality. This can be avoided by applying design patterns like the observer pattern.

Example

OrderService needs Billing to charge an order, and Billing calls back into OrderService to mark it paid. Neither class can be compiled, tested or reused without the other.

class OrderService {
    private final Billing billing = new Billing(this);

    void place(Order order) { billing.charge(order); }
    void markPaid(Order order) { order.paid = true; }
}

class Billing {
    private final OrderService orders;

    Billing(OrderService orders) { this.orders = orders; }

    void charge(Order order) {
        // ... charge the card ...
        orders.markPaid(order); // Billing calls back into OrderService
    }
}

The callback goes through a small interface that the billing module owns, as in the Observer pattern:

interface PaymentListener {
    void paid(Order order);
}

class Billing {
    private final PaymentListener listener;

    Billing(PaymentListener listener) { this.listener = listener; }

    void charge(Order order) {
        // ... charge the card ...
        listener.paid(order);
    }
}

class OrderService implements PaymentListener {
    private final Billing billing = new Billing(this);

    void place(Order order) { billing.charge(order); }

    @Override
    public void paid(Order order) { order.paid = true; }
}

Billing now depends only on PaymentListener, so the dependency between the modules points one way: orders → billing.

Adapted from the Wikipedia article “Circular dependency” (revision 1303252600), available under the CC BY-SA 4.0 license; the text has been edited and examples added.