Suggest an editImprove this articleRefine the answer for “What is a "circular dependency"?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)A **circular dependency** happens when two or more modules import each other, directly or through a chain; Node.js breaks the cycle and hands one of the modules a partially loaded (incomplete) object, instead of looping forever. **Key point:** ES Modules survive cycles better than CommonJS, because their imports are live bindings, not static copies of a value.Shown above the full answer for quick recall.Answer (EN)Image## 1. Definition > A **circular dependency** is a situation where **two (or more) modules depend on each other, directly or indirectly**, forming a **closed loop of dependencies**. In other words: - **Module A** imports **module B** - **Module B** imports **module A** Or worse, through a chain: ```javascript A → B → C → A ``` ## 2. A simple example in Node.js ### a.js ```javascript const b = require('./b'); console.log('Module A loaded'); module.exports = 'A'; ``` ### b.js ```javascript const a = require('./a'); console.log('Module B loaded'); module.exports = 'B'; ``` ### main.js ```javascript const a = require('./a'); const b = require('./b'); ``` ### The result at runtime: ```javascript Module B loaded Module A loaded ``` Notice: the order is **not** what you'd expect. ## 3. What actually happens under the hood When Node.js hits a `require()`, it: 1. Starts loading module `a`; 2. Sees that `a` requires `b`; 3. Starts loading `b`; 4. But `b` also requires `a`, and `a` **hasn't finished loading yet**; 5. Node.js hands `b` a **partially loaded (incomplete)** `a` object. So with a circular dependency, one of the modules ends up with an **incomplete export** (`undefined` or a partial object). ## 4. Demonstrating a partially loaded module ### a.js ```javascript exports.loaded = false; const b = require('./b'); console.log('b.loaded =', b.loaded); exports.loaded = true; console.log('a loaded'); ``` ### b.js ```javascript exports.loaded = false; const a = require('./a'); console.log('a.loaded =', a.loaded); exports.loaded = true; console.log('b loaded'); ``` ### main.js ```javascript require('./a'); ``` ### Output: ```javascript a.loaded = false b loaded b.loaded = true a loaded ``` In other words, when `b` imported `a`, it got an **unfinished** object, `a.loaded` was `false`. ## 5. Why this happens Node.js caches every module on `require()`. As a module loads, it's added to `require.cache`, even if it hasn't finished executing. So with a circular dependency, one of the modules "sees" an **under-loaded version** of the other. ## 6. In real life These cycles often appear **not directly**, but through a chain of imports: ```javascript A → utils.js → logger.js → A ``` or in an architecture like: ```javascript controller → service → model → controller ``` This is usually a sign of **poor design** (modules too tightly coupled). ## 7. Signs you have a "circular dependency" - An import returns `undefined`; - Code runs in an unexpected order; - Node.js prints a warning at startup: ```javascript Warning: Accessing non-existent property 'X' of module exports inside circular dependency ``` - Behavior is unstable or differs between environments. ## 8. How to avoid circular dependencies ### Option 1: break the cycle with a third module Create a "shared" module that holds what both need. **Bad:** ```javascript A → B → A ``` **Good:** ```javascript A → Common B → Common ``` `common.js` ```javascript module.exports = { sharedValue: 42 }; ``` `a.js` ```javascript const common = require('./common'); ``` `b.js` ```javascript const common = require('./common'); ``` ### Option 2: use lazy loading (`require()` inside a function) If a module is only needed **when a function is called**, `require()` it later: ```javascript function useB() { const b = require('./b'); // loads only when called b.doSomething(); } ``` This breaks the "moment of the cycle" at app startup. ### Option 3: change the architecture - Split responsibilities between modules; - Get rid of "mutual" dependencies, let one module own the other; - Use **dependency injection**. ### Option 4: use ESM (ES Modules) In ES Modules (with `import`/`export`), circular dependencies **resolve correctly**, because imports create **live bindings**, not static values like in CommonJS. An example (works reliably): ```javascript // a.mjs import { b } from './b.mjs'; export const a = 'A'; console.log('a sees b =', b); // b.mjs import { a } from './a.mjs'; export const b = 'B'; console.log('b sees a =', a); ``` ## 9. Intuitively: why this is a problem Picture two files launching each other at the same time: ```javascript A launches B → B launches A → A launches B → ... ``` To avoid going on forever, Node.js "stops" the cycle and returns whatever it **managed to load** at that point. That's why you see "half an object". ## 10. Tools for finding cycles For a large project, there are utilities: ```javascript npm install madge -g madge src/ ``` It shows which files form circular dependencies. ## Quick summary | Term | Meaning | |---|---| | **Circular dependency** | Modules import each other (directly or through a chain) | | **Cause** | A module requires another one that hasn't finished running yet | | **Consequence** | `undefined` or a partially loaded module comes back | | **Fixes** | Splitting logic, lazy imports, refactoring, ESM | ## In one sentence > A **circular dependency** is a situation where Node.js modules import each other, forming a closed loop, so one of them loads incompletely, which leads to errors and `undefined` on `require()`.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.