How to fix Circular dependency between project references in TypeScript
This TypeScript error occurs when project references create a circular dependency chain, preventing the compiler from determining build order. Fix it by restructuring your project references to eliminate cycles, using composite projects, or adjusting tsconfig.json files.
Quick fix
Fixes Circular dependency between project references
// Example tsconfig.json with references
{
"compilerOptions": {
"composite": true
},
"references": [
{ "path": "../project-b" } // Check if this creates a cycle
]
}- Identify the circular dependency chain
- Break the cycle by restructuring dependencies
- Use composite projects for complex structures
- Verify and test the fix
// Example tsconfig.json with references{"compilerOptions": {"composite": true},"references": [{ "path": "../project-b" } // Check if this creates a cycle]}Circular dependency between project references
What this error means
The "Circular dependency between project references" error occurs in TypeScript when you have a circular chain of project references in your TypeScript configuration. Project references allow TypeScript projects to depend on other projects, but they must form a directed acyclic graph (DAG) - meaning no cycles are allowed. When TypeScript encounters circular project references, it cannot determine a valid build order. For example, if Project A references Project B, Project B references Project C, and Project C references Project A, the compiler gets stuck in an infinite loop trying to figure out which project to build first. This error typically appears when running `tsc --build` or when using composite projects with references. The compiler detects the cycle and stops with this error message to prevent infinite recursion or undefined build behavior.
Typical symptoms
- TypeScript build fails with error: "Circular dependency between project references"
- Running `tsc --build` or `tsc -b` fails immediately
- Build process hangs or crashes when trying to compile multiple projects
- IDE/editor shows build errors in projects with circular references
- Incremental builds fail to work correctly across referenced projects
Common causes
- Direct circular reference: Project A references Project B, and Project B references Project A
- Indirect circular reference: A chain of three or more projects that eventually references back to the start
- Accidental self-reference: A project incorrectly references itself in its tsconfig.json
- Complex monorepo structure with bidirectional dependencies between packages
- Refactoring or restructuring projects without updating all reference paths
How to fix "Circular dependency between project references"
First, examine your project structure to identify where the circular dependency occurs. Check all tsconfig.json files in your project:
// Example tsconfig.json with references
{
"compilerOptions": {
"composite": true
},
"references": [
{ "path": "../project-b" } // Check if this creates a cycle
]
}Look for any project that references another project which eventually references back to the original. You can create a visual diagram or list all references to trace the chain.
Restructure your project dependencies to eliminate the cycle. Consider these approaches:
1. Extract shared code: Move common code that both projects depend on into a third shared project
2. Merge projects: If two projects are tightly coupled, consider merging them into one
3. Change dependency direction: Determine which project should truly depend on the other
Example of fixing a direct cycle:
Before: Project A ↔ Project B (circular)
After: Project A → Shared → Project B (no cycle)For complex monorepos, use TypeScript's composite project feature properly:
// In root tsconfig.json
{
"files": [],
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/ui" },
{ "path": "./packages/utils" }
]
}Each sub-project should have "composite": true and should only reference projects it truly depends on, not projects that depend on it.
After restructuring, verify the fix works:
1. Run tsc --build --verbose to see the build order
2. Check that all projects compile successfully
3. Test that your application still works correctly
4. Consider adding a pre-build script to detect cycles:
#!/bin/bash
# Simple cycle detection (pseudo-code)
# Check that no project references create cycles
# You could write a script that parses all tsconfig.json filesImplement practices to prevent circular dependencies from reoccurring:
1. Document dependency rules: Clearly define which projects can depend on others
2. Use dependency graphs: Tools like madge or dependency-cruiser can detect cycles
3. CI/CD checks: Add cycle detection to your build pipeline
4. Code reviews: Review tsconfig.json changes carefully when adding new references
Example using dependency-cruiser:
npx dependency-cruiser --config .dependency-cruiser.json src/Create a .dependency-cruiser.json config file to define allowed and forbidden dependencies.
Advanced notes
## Advanced Considerations
### TypeScript Build Mode
When using tsc --build, TypeScript performs several optimizations:
- Incremental compilation across projects
- Proper ordering of build steps
- Shared type checking between projects
Circular dependencies break these optimizations because the compiler cannot determine a valid topological order.
### Monorepo Tooling Integration
If you're using monorepo tools like:
- Nx: Has built-in dependency graph management
- Lerna: May need custom scripts for TypeScript project references
- Turborepo: Supports task dependencies that can help prevent cycles
These tools often provide better dependency management than manual tsconfig.json references.
### Performance Implications
Circular dependencies can cause:
- Slower build times due to repeated compilation
- Increased memory usage from duplicated type checking
- Difficulty with incremental builds
### Alternative Approaches
For some use cases, consider:
1. Path mapping: Use paths in tsconfig instead of project references for simple cases
2. Single composite project: Combine related code into one larger project
3. Build tool integration: Let your build tool (Webpack, Rollup) handle dependency resolution
### Debugging Tips
Use tsc --build --dry --verbose to see what TypeScript would build without actually building. This can help identify the circular chain.
Frequently asked questions
- What does "Circular dependency between project references" mean?
- The "Circular dependency between project references" error occurs in TypeScript when you have a circular chain of project references in your TypeScript configuration. Project references allow TypeScript projects to depend on other projects, but they must form a directed acyclic graph (DAG) - meaning no cycles are allowed. When TypeScript encounters circular project references, it cannot determine a valid build order.
- How do I fix "Circular dependency between project references"?
- This TypeScript error occurs when project references create a circular dependency chain, preventing the compiler from determining build order. Fix it by restructuring your project references to eliminate cycles, using composite projects, or adjusting tsconfig.json files.
- What causes "Circular dependency between project references"?
- Direct circular reference: Project A references Project B, and Project B references Project A Indirect circular reference: A chain of three or more projects that eventually references back to the start Accidental self-reference: A project incorrectly references itself in its tsconfig.json
- Is the "Circular dependency between project references" error serious?
- It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- 📚TypeScript Official Documentation
- 📖Typescriptlang.org - project-references.html(Official Docs)
- 🐙Github.com - 38857(GitHub)
- 📖Typescriptlang.org - references.html(Official Docs)
- 💬
- 🐙Github.com - 25376(GitHub)