
Typescript Circular Dependency
- 2.4k repo stars
- Updated February 21, 2026
- blader/claudeception
typescript-circular-dependency is a Claude Code skill that detects and resolves circular import dependencies in TypeScript and JavaScript.
About
This skill detects and resolves circular import dependencies in TypeScript and JavaScript. A developer uses it when an import is unexpectedly undefined or they hit 'Cannot access X before initialization' at runtime while the code compiles fine. It shows how to detect cycles with madge and gives five resolution strategies plus CI checks to prevent regressions.
- Detects and resolves TypeScript/JavaScript circular import dependencies
- Triggers on 'Cannot access X before initialization' and undefined imports
- Uses madge to detect cycles; offers 5 resolution strategies including type-only imports
Typescript Circular Dependency by the numbers
- Data as of Aug 5, 2026 (Skillselion catalog sync)
typescript-circular-dependency capabilities & compatibility
- Capabilities
- debugging · refactoring · dependency analysis
- Use cases
- debugging · refactoring
What typescript-circular-dependency says it does
Circular dependencies occur when module A imports from module B, which imports (directly or indirectly) from module A.
TypeScript `import type` is your friend—it's erased at runtime and can't cause cycles
Barrel files (`index.ts`) are a common source of accidental cycles
npx skills add https://github.com/blader/claudeception --skill typescript-circular-dependencyAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| repo stars | ★ 2.4k |
|---|---|
| Last updated | February 21, 2026 |
| Repository | blader/claudeception ↗ |
What it does
Trace and break a TypeScript circular import causing undefined values or 'cannot access before initialization' at runtime.
Who is it for?
Untangling service-to-service, barrel-file, or type-import cycles in TypeScript projects.
Skip if: Errors that reproduce at TypeScript compile time rather than runtime.
When should I use this skill?
You hit 'Cannot access X before initialization', an import is unexpectedly undefined, or type errors shift when you change import order.
What you get
The dependency cycle is detected and broken so previously undefined imports resolve correctly at runtime.
- A cycle-free import graph verified by madge --circular
By the numbers
- 5 resolution strategies
- 3 common circular-dependency patterns
- 4-step verification
Files
TypeScript Circular Dependency Detection and Resolution
Problem
Circular dependencies occur when module A imports from module B, which imports (directly or indirectly) from module A. TypeScript compiles successfully, but at runtime, one of the imports evaluates to undefined because the module hasn't finished initializing yet.
Context / Trigger Conditions
Common error messages:
ReferenceError: Cannot access 'UserService' before initializationTypeError: Cannot read properties of undefined (reading 'create')TypeError: (0 , _service.doSomething) is not a functionSymptoms that suggest circular imports:
- Import is
undefinedeven though the export exists - Error only appears at runtime, not during TypeScript compilation
- Moving an import statement changes which import is undefined
- Tests fail but the app works (or vice versa)
- Adding
console.logat the top of a file changes behavior
Solution
Step 1: Detect the Cycle
Use a tool to visualize dependencies:
# Install madge
npm install -g madge
# Find circular dependencies
madge --circular --extensions ts,tsx src/
# Generate visual graph
madge --circular --image graph.svg src/Or use the TypeScript compiler:
# Check for cycles (requires tsconfig setting)
npx tsc --listFiles | head -50Step 2: Identify the Pattern
Common circular dependency patterns:
Pattern A: Service-to-Service
services/userService.ts → services/orderService.ts → services/userService.tsPattern B: Type imports
types/user.ts → types/order.ts → types/user.tsPattern C: Index barrel files
components/index.ts → components/Button.tsx → components/index.tsStep 3: Resolution Strategies
Strategy 1: Extract Shared Dependencies
Before:
// userService.ts
import { OrderService } from './orderService';
export class UserService { ... }
// orderService.ts
import { UserService } from './userService';
export class OrderService { ... }After:
// types/interfaces.ts (new file - no imports from services)
export interface IUserService { ... }
export interface IOrderService { ... }
// userService.ts
import { IOrderService } from '../types/interfaces';
export class UserService implements IUserService { ... }Strategy 2: Dependency Injection
// orderService.ts
export class OrderService {
constructor(private userService: IUserService) {}
// Instead of importing UserService directly
}
// main.ts
const userService = new UserService();
const orderService = new OrderService(userService);Strategy 3: Dynamic Imports
// Only import when needed, not at module level
async function processOrder() {
const { UserService } = await import('./userService');
// ...
}Strategy 4: Use Type-Only Imports
If you only need types (not values), use type-only imports:
// This doesn't create a runtime dependency
import type { User } from './userService';Strategy 5: Restructure Barrel Files
Before (problematic):
// components/index.ts
export * from './Button';
export * from './Modal'; // Modal imports Button from './index'After:
// components/Modal.tsx
import { Button } from './Button'; // Direct import, not from indexStep 4: Prevent Future Cycles
Add to your CI/build process:
// package.json
{
"scripts": {
"check:circular": "madge --circular --extensions ts,tsx src/"
}
}Or configure ESLint:
// .eslintrc.js
module.exports = {
plugins: ['import'],
rules: {
'import/no-cycle': ['error', { maxDepth: 10 }]
}
}Verification
1. Run madge --circular src/ - should report no cycles 2. Run your test suite - previously undefined imports should work 3. Delete node_modules and reinstall - app should still work 4. Build for production - no runtime errors
Example
Problem: OrderService is undefined when imported in UserService
Detection:
$ madge --circular src/
Circular dependencies found!
src/services/userService.ts → src/services/orderService.ts → src/services/userService.tsFix: Extract shared interface
// NEW: src/types/services.ts
export interface IOrderService {
createOrder(userId: string): Promise<Order>;
}
// MODIFIED: src/services/userService.ts
import type { IOrderService } from '../types/services';
export class UserService {
constructor(private orderService: IOrderService) {}
}
// MODIFIED: src/services/orderService.ts
// No longer imports UserService
export class OrderService implements IOrderService {
async createOrder(userId: string): Promise<Order> { ... }
}Notes
- TypeScript
import typeis your friend—it's erased at runtime and can't cause cycles - Barrel files (
index.ts) are a common source of accidental cycles - The order of exports in a file can matter when there's a cycle
- Jest/Vitest may handle module resolution differently than your bundler
- Some bundlers (Webpack, Vite) have better cycle handling than others
require()can sometimes mask circular dependency issues thatimportexposes
Related skills
FAQ
How do I detect circular dependencies?
Install madge and run 'madge --circular --extensions ts,tsx src/', which lists cycles and can render a visual graph.
What is the easiest way to break a cycle?
Use type-only imports ('import type') when you only need types, since they are erased at runtime and cannot cause cycles.