How to Measure Codebase Complexity for Developer Onboarding (Updated 10JUN26)
Use dependency analysis, fan-in, fan-out, circular dependencies, test coverage, and graph depth to identify which parts of a codebase are hardest for new developers to learn.
Ask an engineering leader a simple question:
How long does it take a new developer to become productive in your codebase?
The answer is usually something like:
- “A few weeks, maybe.”
- “It depends on their background.”
- “It varies by team.”
- “Honestly, we are not sure.”
That uncertainty is expensive. As a rough internal planning estimate, a mid-level engineer with a fully loaded cost of $150,000 per year costs about $75 per hour. If ramp-up time varies by ten working days depending on which modules a new hire touches first, that is thousands of dollars in salary alone before you include senior engineer support, delayed delivery, and mistakes caused by incomplete system understanding.
Most teams do not measure this. They treat onboarding difficulty as a people problem, a documentation problem, or a manager intuition problem.
But onboarding is also a codebase structure problem.
A new developer learns a system by following dependencies: routes to services, services to models, models to infrastructure, utilities to shared behavior, and so on. If those paths are shallow, well-tested, and directional, onboarding is easier. If they are tangled, circular, highly coupled, and poorly tested, onboarding is harder.
You can measure that.
To measure codebase complexity for onboarding, start with structural metrics such as fan-out, fan-in, circular dependencies, dependency depth, test coverage, and documentation quality. Then rank modules relative to each other inside the same codebase. The goal is not to produce a universal “difficulty score.” The goal is to identify which parts of your system are safe starting points, which parts require guidance, and which parts create the highest ramp-up risk.
Code Complexity vs. Onboarding Complexity
Code complexity and onboarding complexity overlap, but they are not the same thing.
A function with many branches may be hard to understand in isolation. That is traditional code complexity.
A module with simple internal logic may still be hard to onboard into if it imports 30 other modules, is used by half the system, and sits inside a circular dependency cluster. That is onboarding complexity.
For a new developer, the question is rarely:
Is this file complicated?
The more important question is:
How much of the system do I need to understand before I can safely change this file?
That is what dependency analysis helps reveal.
A module can be:
- Easy to read but risky to change because many other modules depend on it.
- Complex internally but safe to learn because it has strong tests and few dependents.
- Small but confusing because it sits inside a circular dependency.
- Large but approachable because it has clean boundaries and obvious inputs and outputs.
Onboarding metrics should capture those differences.
The Onboarding Problem
Consider a scenario like this: a software team hires six engineers over three months. Despite similar technical backgrounds, their time-to-first-meaningful-PR varies widely:
Developer A: 3 days
Developer B: 12 days
Developer C: 8 days
Developer D: 18 days
Developer E: 5 days
Developer F: 11 days
Average: 9.5 days
High variance across assignments
Exit interviews reveal very different experiences.
Developer A:
The payment module was easy to understand. Clear inputs, clear outputs, obvious where my change should go.
Developer D:
I could not figure out how anything connected. Every file imported everything else. I would make a change and 20 tests would break. I never felt confident.
What changed?
Not necessarily the developer. Not necessarily the onboarding docs. Often, the first assignment sent each developer through a different part of the codebase.
One person started in a clean, shallow, well-tested module. Another started in a tangled, high-blast-radius area with hidden dependencies.
Without structural visibility, both assignments can look equivalent in a backlog. They are not equivalent for onboarding.
A Minimum Viable Metric Set
You do not need a perfect model to improve onboarding. Start with a small set of metrics that are easy to explain and hard to ignore.
A practical minimum set is:
- Fan-out — how many modules this module imports.
- Fan-in — how many modules import this module.
- Circular dependency membership — whether the module is part of a strongly connected component.
- Test coverage or test presence — whether a new developer has executable examples to lean on.
That alone gives you a useful first pass.
| Metric | Why it matters for onboarding |
|---|---|
| High fan-out | The module requires more prerequisite context. |
| High fan-in | Changes have wider blast radius. |
| Circular dependency membership | There may be no clean starting point for understanding. |
| Low test coverage | New developers have less behavioral guidance and safety. |
Once you have those, you can add more nuance with dependency depth, documentation quality, churn, ownership, and cyclomatic complexity.
The key is to rank modules relative to other modules in the same repository. You are not comparing your codebase to another company’s codebase. You are identifying where your own onboarding friction is concentrated.
An Onboarding Difficulty Rubric
A scoring rubric can make module comparison easier. Treat the following as an example, not a universal formula:
Onboarding Difficulty Score =
(Cyclomatic Complexity × 0.1)
+ (Fan-out × 2)
+ (Fan-in × 0.5)
+ (Circular Dependencies × 10)
+ (Depth in Dependency Graph × 1)
- (Test Coverage × 0.05)
- (Documentation Score × 0.3)
The weights are intentionally adjustable. The point is not that fan-out is always worth exactly two points or that documentation always subtracts exactly 0.3 points. The point is to create a repeatable way to compare modules inside one system.
Use the rubric to sort modules into buckets:
0–15 Very easy
15–30 Easy
30–50 Moderate
50–80 Difficult
80+ Very difficult
The exact thresholds matter less than consistency. If you apply the same rubric across the codebase, the outliers become visible.
What Each Metric Captures
Cyclomatic Complexity
Cyclomatic complexity measures the number of independent execution paths through code.
A function with many branches requires a reader to hold many possible states in mind. For a new developer still building a mental model of the system, that increases cognitive load.
Cyclomatic complexity is useful, but it should not dominate onboarding analysis. A highly branched function with excellent tests may be easier to learn than a small file that imports half the system.
Fan-out
Fan-out measures how many modules this module imports.
For onboarding, fan-out is often one of the most important signals. When a developer opens a file with 34 imports, the file comes with a prerequisite list. Understanding it fully may require understanding services, models, utilities, configuration, external clients, and framework glue.
High fan-out creates a “you must be this tall to ride” barrier. It does not mean the module is bad, but it does mean it may be a poor first assignment.
Fan-in
Fan-in measures how many modules import this module.
High fan-in means a module is load-bearing. Changing it can affect many parts of the system.
High fan-in does not necessarily make code harder to read, but it makes code harder to change confidently. New developers often discover this the hard way: a small utility change breaks tests in areas they did not know existed.
For onboarding, high fan-in modules usually need extra context, stronger tests, or pairing.
Circular Dependencies
Circular dependencies are especially disruptive for new developers.
When module A imports B and B imports A, there is no clean entry point. The developer bounces between files without reaching stable ground. Larger cycles are worse: a strongly connected component with five or ten modules often signals a boundary problem.
Cycles do not always break at runtime, but they make architecture harder to explain. If a new developer asks “where does this responsibility live?” and the answer is “sort of everywhere,” onboarding friction increases.
Dependency Depth
Dependency depth measures how far a module sits from common entry points.
A module seven layers away from an API route may require a new developer to understand several levels of abstraction before the code makes sense in context.
Deep modules are not inherently bad. Many infrastructure or domain internals are deep for good reasons. But deep modules are rarely ideal starting points unless the onboarding path explicitly walks the developer down to them.
Test Coverage
Tests reduce onboarding difficulty because they provide executable examples.
A new developer can read tests to understand expected behavior, edge cases, inputs, outputs, and failure modes. Good tests also make changes less frightening.
Coverage percentage is an imperfect metric, but as part of an onboarding rubric it is still useful. A high-complexity module with strong tests is much easier to approach than a high-complexity module with no safety net.
Documentation Quality
Documentation fills the gap between what the code does and what a new reader can infer quickly.
This does not need to be elaborate. A module-level docstring, a short README, meaningful comments around non-obvious decisions, or a diagram can materially reduce onboarding difficulty.
Documentation scoring is subjective, but that is acceptable if applied consistently. A rough 1–10 rating is enough to expose neglected modules.
Example Module Scores
Using the rubric above, two modules might look very different.
Payment Module
Cyclomatic Complexity: 15 → +1.5
Fan-out: 3 → +6.0
Fan-in: 18 → +9.0
Circular Dependencies: 0 → +0.0
Depth: 2 → +2.0
Test Coverage: 94% → -4.7
Documentation: 8/10 → -2.4
Total: 11.4
Bucket: Low onboarding difficulty
This module has meaningful fan-in, so changes may matter, but it has low fan-out, no cycles, strong tests, and good documentation. It is probably a reasonable onboarding target with modest guidance.
Legacy Integration Module
Cyclomatic Complexity: 87 → +8.7
Fan-out: 34 → +68.0
Fan-in: 12 → +6.0
Circular Dependencies: 3 → +30.0
Depth: 7 → +7.0
Test Coverage: 23% → -1.15
Documentation: 2/10 → -0.6
Total: 117.95
Bucket: Very high onboarding difficulty
This module is not just complex. It is dependency-heavy, cyclic, deep, weakly tested, and poorly documented. That does not mean nobody should touch it. It means assigning it to a new developer in week one is likely to create avoidable friction.
The value is not the exact score. The value is that the difference is visible before someone struggles.
Visualizing Onboarding Paths
New developers do not learn codebases randomly. They follow paths created by their first assignments.
A structural dependency graph lets you map those paths explicitly.
For an entry point, trace direct and transitive dependencies, score each module, and identify where complexity increases.
Example:
Level 1: Entry point
api/ProductController
Score: 8.3 — Good starting point
Level 2:
├─ services/ProductService Score: 12.7
├─ validators/ProductValidator Score: 6.2
└─ dto/ProductDTO Score: 3.1
Level 3:
├─ repositories/ProductRepository Score: 15.8
├─ domain/Product Score: 9.4
└─ infrastructure/Database Score: 34.7
Complexity spike
Level 4:
└─ legacy/DataMigration Score: 89.2
High-complexity dependency
The important signal is not just the score of one module. It is the gradient along the path.
A path that goes from 8 to 12 to 15 is a reasonable learning curve. A path that jumps from 8 to 89 in the second week needs support.
Once you know where the spikes are, you can make better onboarding decisions:
- Route early assignments around high-complexity nodes.
- Pair deliberately when a new developer must touch a difficult area.
- Create short guides for known danger zones.
- Add tests before assigning work in high-risk modules.
- Use complexity spikes to prioritize refactoring.
This turns onboarding from “whatever ticket is available” into a sequenced learning path.
Module Complexity Heatmaps
Once you score modules, grouping by directory reveals structural patterns:
Very Easy:
dto/
simple validators/
typed request/response objects/
Easy:
controllers/
route handlers/
small services/
Moderate:
repositories/
domain services/
integration wrappers/
Difficult:
legacy integrations/
shared utility clusters/
orchestration code/
Very Difficult:
core engine/
old platform layer/
cycle-heavy modules/
This helps answer practical questions:
- Where should new developers start?
- Which directories need better documentation?
- Which modules should require pairing?
- Where would refactoring improve onboarding most?
- Which areas are too risky for first-month assignments?
A heatmap also makes complexity easier to discuss. Instead of saying “the legacy area is rough,” you can say:
The first onboarding path hits three modules over 70, all with low test coverage. We should either sequence around that path or add support before assigning it.
Prioritizing What to Fix
Not all high-complexity modules are equally urgent.
A module with a high score but low churn and no connection to common onboarding paths may not matter immediately. A moderate-complexity module that every new hire touches in week one may be more important.
Prioritize modules with three traits:
- High onboarding difficulty score
- Early position in common onboarding paths
- Low test coverage or weak documentation
These are the modules most likely to produce onboarding friction.
You can also combine structural metrics with team experience:
- Which modules generate the most questions from new hires?
- Which files do senior engineers warn people about?
- Which areas produce the most review comments?
- Which parts of the system are avoided because “only one person understands them”?
If structural scores and human experience agree, you have a high-confidence target. If they disagree, investigate. The metric may be missing implicit domain complexity, or the team may have informal support mechanisms that are not visible in the graph.
What Complexity-Aware Onboarding Looks Like
The difference between random onboarding and structured onboarding is sequencing.
Unstructured onboarding
- Assign whatever ticket is available.
- Treat all modules as equally approachable.
- Let new hires discover dangerous areas by accident.
- Rely on senior engineers to explain complexity reactively.
- Measure onboarding only after someone struggles.
Complexity-aware onboarding
- Start with low-complexity, well-tested modules.
- Increase graph depth gradually.
- Pair at known complexity spikes.
- Avoid high-blast-radius modules in the first assignments.
- Document the areas that cannot be simplified yet.
- Track whether ramp-up becomes more consistent.
The workload may be the same. The path changes the experience.
A Practical 30-Day Onboarding Plan
A complexity-aware plan does not need to be heavy.
Week 1: Start with low-risk modules
Choose modules with:
- Low fan-out
- Low or moderate fan-in
- No circular dependency membership
- Clear tests
- Good local documentation
The goal is early contribution without forcing the developer to understand the entire system.
Week 2: Introduce common paths
Assign work that moves one level deeper into the graph:
route → service → repository
or:
UI component → API client → backend endpoint
This helps the developer build a system map gradually.
Week 3: Pair at complexity spikes
If a new assignment touches a high-score module, pair intentionally. Do not wait for the developer to get stuck.
A senior engineer can explain:
- why the module is shaped this way
- which dependencies matter
- which tests provide safety
- which areas are dangerous
- what not to change casually
Week 4: Calibrate the metric
Ask the developer:
- Which modules were easiest to understand?
- Which modules were surprisingly hard?
- Where did the dependency path become unclear?
- Which tests or docs helped most?
- Which assignment should have come later?
Compare those answers against your scores. Adjust the rubric.
The goal is not to create a perfect score. The goal is to make onboarding friction visible enough to improve.
How Dependency Analysis Helps
Dependency analysis provides the structural map behind the rubric.
A dependency graph can show:
- which modules import many others
- which modules are heavily depended on
- where circular dependencies exist
- how deep a module sits from an entry point
- which clusters are tightly coupled
- which files sit on common paths through the system
PViz is designed to expose this kind of structure. The hosted app can analyze a repository and surface dependency relationships, cycles, and graph structure for review.
The important point is not that one score tells the whole story. It does not.
The important point is that dependency structure gives you a measurable starting point. Once the structure is visible, onboarding decisions can become more intentional.
Track the Human Signals Too
Structural metrics should be calibrated against human experience.
Track simple onboarding signals:
- time to first meaningful PR
- number of review cycles on first assignments
- modules touched in the first two weeks
- questions asked by module or subsystem
- self-reported confidence at 30 and 60 days
- “dangerous modules” named by senior engineers
Then compare those signals to your structural map.
If a high-score module causes lots of questions, the metric is probably capturing a real issue. If a low-score module causes lots of confusion, look for missing domain context, unclear naming, poor documentation, or hidden runtime behavior. If a high-score module causes few problems, tests or mentoring may be offsetting the complexity.
This is how the rubric improves over time.
Refactor Strategically
Once you know where onboarding friction lives, resist the urge to refactor everything.
Focus on high-leverage targets:
- high-complexity modules on early onboarding paths
- circular dependencies near common entry points
- high-fan-out modules with weak tests
- high-fan-in modules with unclear ownership
- modules that new hires consistently identify as confusing
Improvements do not have to be dramatic.
You can:
- add tests around high-risk behavior
- split a utility module by responsibility
- remove unnecessary imports
- extract shared interfaces
- break circular dependencies
- add a short module guide
- document dependency paths
- create safer first tickets
The best onboarding improvements are often small structural changes in the places new developers encounter first.
Broader Patterns You Are Likely to See
When you map onboarding complexity across a codebase, certain patterns tend to appear:
- Smaller systems usually have shallower dependency graphs.
- Modules with clear boundaries are easier to approach independently.
- Circular dependencies create confusion about where to begin.
- Well-tested modules provide implicit guidance.
- High fan-out modules are poor first assignments.
- High fan-in modules require extra caution.
- Legacy directories often concentrate onboarding risk.
- Documentation gaps matter most where structural complexity is already high.
These are not universal laws. They are tendencies. Your system will have its own exceptions.
That is why measurement matters.
Action Plan
Step 1: Build a baseline
Generate a dependency graph and collect a minimum metric set:
- fan-out
- fan-in
- circular dependency membership
- test coverage or test presence
Group modules into difficulty buckets.
Step 2: Compare against experience
Ask recent hires and senior engineers where the codebase feels hardest to understand. Compare those answers to the structural scores.
Step 3: Identify onboarding paths
Map the entry points used for common first assignments. Trace their dependencies and identify complexity spikes.
Step 4: Sequence first assignments
Start new developers in low-risk modules. Move deeper gradually. Pair at known spikes.
Step 5: Improve high-leverage modules
Prioritize high-complexity, low-test modules that appear early in onboarding paths.
Step 6: Track change
Measure whether onboarding becomes more consistent over time. Track time to first meaningful PR, confidence at 30 days, and recurring question areas.
Conclusion: Onboarding Is Measurable
“How long does onboarding take?” does not have to be a guess.
With structural analysis, you can:
- compare module difficulty
- identify onboarding risks
- design better learning paths
- prioritize refactoring
- track whether onboarding improves
Onboarding is part of developer experience, and developer experience is shaped by the codebase.
Your system already contains signals about how easy it is to understand. Dependency analysis makes those signals visible.
Start with a simple metric set. Find the obvious complexity spikes. Sequence early assignments around them. Then improve the modules that create the most friction.
That is how onboarding moves from intuition to engineering.
Measure onboarding complexity in your codebase: Generate a dependency graph and look for high fan-out modules, high fan-in modules, circular dependencies, and complexity spikes along common onboarding paths. PViz can help visualize these relationships in the hosted app for supported repositories.
Try PViz on your own codebase
Get dependency graphs, coupling signals, and a compressed bundle ready for your LLM — for any GitHub repository, in minutes.