CircularDependencyError¶
Overview¶
CircularDependencyError is raised when @lifecycle components have circular
dependencies that prevent the container from determining initialization order.
Important
This error only applies to @lifecycle components during container.start().
Regular services without @lifecycle can have circular dependencies (though not
recommended) because they’re instantiated lazily.
Example Error¶
Circular Dependency: Circular dependency detected
Context:
- unprocessed: {<ServiceA>, <ServiceB>}
-> See: https://dioxide.readthedocs.io/en/stable/troubleshooting/circular-dependency.html
What Causes This¶
A circular dependency exists when components depend on each other in a loop:
ServiceA -> depends on -> ServiceB -> depends on -> ServiceA
The container cannot determine which component to initialize first because each depends on another being already initialized.
Example¶
from dioxide import service, lifecycle, Container, Profile
@service
@lifecycle
class ServiceA:
def __init__(self, b: 'ServiceB'): # Depends on B
self.b = b
async def initialize(self) -> None:
...
async def dispose(self) -> None:
...
@service
@lifecycle
class ServiceB:
def __init__(self, a: ServiceA): # Depends on A - CYCLE!
self.a = a
async def initialize(self) -> None:
...
async def dispose(self) -> None:
...
container = Container(profile=Profile.PRODUCTION)
await container.start() # CircularDependencyError!
Types of Circular Dependencies¶
Direct Cycle¶
Two components directly depend on each other:
A -> B -> A
Indirect Cycle¶
A longer chain forms a loop:
A -> B -> C -> D -> A
Self-Dependency¶
A component depends on itself (rare):
A -> A
Solutions¶
1. Break Dependency with Interface¶
Instead of depending on a concrete class, depend on a port (Protocol):
from typing import Protocol
class CachePort(Protocol):
def get(self, key: str) -> Any: ...
@service
@lifecycle
class ServiceA:
def __init__(self, cache: CachePort): # Depend on abstraction
self.cache = cache
2. Remove @lifecycle from One Component¶
If only one component truly needs lifecycle management:
@service # No @lifecycle - lazy initialization
class ServiceB:
def __init__(self, a: ServiceA):
self.a = a
@service
@lifecycle # Only this one has lifecycle
class ServiceA:
async def initialize(self) -> None:
...
3. Lazy Resolution¶
Defer resolution to first use:
@service
@lifecycle
class ServiceA:
def __init__(self, container: Container):
self.container = container
self._b = None
@property
def b(self) -> ServiceB:
if self._b is None:
self._b = self.container.resolve(ServiceB)
return self._b
Debugging Tips¶
Identify the cycle: Look at the “unprocessed” set in the error message
Draw a dependency graph: Visualize dependencies on paper
Find the weakest link: Identify which dependency is least essential
Check @lifecycle usage: Not all components need lifecycle management
Why This Error Exists¶
@lifecycle components need to be initialized in dependency order:
Dependencies must be initialized before dependents
During disposal, dependents must be disposed before dependencies
A cycle makes this impossible - there’s no valid order
Without @lifecycle, services are created lazily on-demand, so cycles don’t
prevent instantiation (though they can cause RecursionError at runtime).
Best Practices¶
Avoid circular dependencies: Design for acyclic dependency graphs
Use hexagonal architecture: Depend on abstractions (ports) at boundaries
Limit @lifecycle: Only use for components that truly need init/dispose
Single Responsibility: Components with clear responsibilities rarely cycle
Integration tests: Test that
container.start()succeeds
See Also¶
Lifecycle Methods: Async/Sync Patterns - Lifecycle management patterns
Hexagonal Architecture with dioxide - How to break dependencies
CaptiveDependencyError - Related scope error