Fix Python Circular Import Error — What It Is and How to Resolve It
Python circular imports cause ImportError or partially-initialized module errors. Here's why they happen and the 4 reliable ways to fix them without restructuring your entire project.
What Is a Circular Import?
Circular import happens when module A imports module B, and module B imports module A. Python starts loading A, sees it needs B, starts loading B, sees it needs A — but A isn't finished loading yet. Python gives B a partially-initialized version of A, causing either ImportError or AttributeError when B tries to use something from A that hasn't been defined yet.
Error:
ImportError: cannot import name 'User' from partially initialized module 'models' (most likely due to a circular import)
Or:
Error:
ImportError: cannot import name 'process_order' from 'services' (circular import)
The stack trace usually shows two files importing each other. That's the circular pair.
How to Diagnose It
The error message often doesn't name the circular pair directly. Use this to find it:
Or add a print at the top of suspected files:
When you see the same module name print twice before the error, that's the circular pair.
For Django projects:
Fix 1: Move the Import Inside the Function
Simplest fix. Instead of importing at module level (top of file), import inside the function that actually needs it. The import runs only when the function is called — by then, both modules are fully loaded.
Fix: Move import inside the method.
Note: Local imports are slightly slower (Python checks sys.modules each call) but the overhead is negligible. In hot paths called thousands of times per second, cache the import result. For normal application code, local imports are fine.
Fix 2: Use TYPE_CHECKING for Type Hints
If the circular import exists only for type hints (you reference a class in a type annotation but don't actually call it), use the TYPE_CHECKING guard. The import runs only during static analysis, never at runtime.
Fix: Guard with
TYPE_CHECKING.
from __future__ import annotations makes all type annotations lazy strings — Python doesn't evaluate them at import time. This eliminates most circular imports caused by type hints.
Fix 3: Extract Shared Code to a Third Module
When A and B both need the same thing, and importing from each other creates the circle, extract that shared code into a new module C that neither A nor B imports back.
This is the correct architectural fix when local imports feel like a hack. Common extraction targets: constants, base classes, type definitions, utility functions.
Fix 4: Restructure Using Dependency Injection
Instead of modules importing each other, pass dependencies as function arguments or constructor parameters.
Fix: Pass the notification function as a parameter.
When Circular Imports Are a Design Signal
Circular imports often mean two modules are too tightly coupled — they each know too much about each other. The four fixes above resolve the import error, but if you find yourself doing multiple local imports or the circle involves 3+ files, it's worth restructuring.
| Pattern | Better approach |
|---|---|
| Model imports service | Model emits signal/event → service listens |
| Service imports model imports service | Extract to shared types.py |
| Utils imports app code | Utils should have no app dependencies |
__init__.py re-exports cause circles | Flatten imports, avoid __init__.py re-exports |
For complex multi-file circular imports, paste the import chain into DebugAI. It traces which module imports which across files and identifies the minimal cut point — which import to move or which file to extract.
Debug faster starting today.
Free VS Code extension. 10 sessions/day. No credit card.