Code Hotspots: Finding the 20% of Code Causing 80% of Problems
Code Hotspots: Finding the 20% of Code Causing 80% of Problems
In 2024, I inherited a codebase with 380,000 lines of code. SonarQube flagged 12,000 code smells. The previous team had quit trying to address them because the number was so overwhelming that no amount of effort seemed to make a dent.
I fixed the most impactful quality issues in 6 weeks, working about 20% of sprint capacity. Not by being smarter or working harder, but by targeting hotspots. Exactly 14 files, out of roughly 2,400, were responsible for 73% of the bugs filed in the previous year. Fix those 14 files, and the metrics transformed.
That's the power of hotspot analysis. Most debt isn't evenly distributed. It's concentrated. Find the concentration, and you can achieve outsized results with minimal investment.
What Is a Code Hotspot?
A hotspot is a file (or module) that scores high on two dimensions simultaneously:
- Change frequency: How often is this code modified?
- Complexity: How hard is this code to understand and change safely?
A complex file that nobody touches isn't a hotspot. It's stable legacy code. Leave it alone. A frequently-changed file that's simple isn't a hotspot. It's healthy active code.
The danger zone is the intersection: complex code that changes often. Every change carries risk, and the changes are frequent. This is where bugs breed.
Finding Hotspots: The Manual Approach
You don't need any special tools to start. Git gives you everything you need.
Step 1: Find Your Most-Changed Files
# Top 20 most-changed files in the last 6 months
git log --since="6 months ago" --name-only --pretty=format: -- '*.ts' '*.tsx' | \
sort | uniq -c | sort -rn | head -20This gives you a list like:
47 src/payments/processor.ts
43 src/api/routes/orders.ts
38 src/auth/session-manager.ts
35 src/components/Dashboard.tsx
31 src/utils/validators.ts
...
Step 2: Measure Complexity
For a quick-and-dirty complexity assessment, use lines of code as a proxy. It's not perfect, but it correlates.
# Lines of code for your most-changed files
wc -l src/payments/processor.ts src/api/routes/orders.ts src/auth/session-manager.tsFor a more accurate picture, use a tool like complexity-report or SonarQube's cognitive complexity metric.
Step 3: Plot the Results
Create a simple scatter plot (a spreadsheet works fine):
| File | Changes (6mo) | Lines of Code | Hotspot Score |
|---|---|---|---|
| payments/processor.ts | 47 | 842 | 39,574 |
| api/routes/orders.ts | 43 | 521 | 22,403 |
| auth/session-manager.ts | 38 | 687 | 26,106 |
| components/Dashboard.tsx | 35 | 234 | 8,190 |
| utils/validators.ts | 31 | 156 | 4,836 |
Hotspot Score = Changes x Lines of Code. Simple but effective.
payments/processor.ts is screaming for attention. 47 changes to a 842-line file means someone is touching this monster nearly twice a week. Every change is a roll of the dice.
Finding Hotspots: The Automated Approach
If you want more precision, these tools do the analysis for you.
CodeScene (my recommendation for teams with budget): Behavioral code analysis that identifies hotspots, tracks trends, and even predicts where bugs will appear based on code patterns. It's the best tool I've used for this specific purpose.
git-of-theseus: Open source tool that analyzes code survival rates. Code that keeps getting rewritten is a hotspot signal.
Code Maat: Adam Tornhill's open source tool for mining Git history. It does hotspot analysis, coupling analysis, and more.
# Using Code Maat to find hotspots
java -jar code-maat.jar -l git_log.txt -c git -a revisionsWhat to Do With Your Hotspots
You've found your top 10 hotspots. Now what? Don't refactor them all at once. Use this triage process.
The Hotspot Triage Checklist
For each hotspot, answer these questions:
-
Why is this file changing so often?
- Multiple unrelated features touch it (coupling problem)
- Bugs keep recurring (quality problem)
- Requirements are genuinely changing (might be fine)
-
What's the impact of its complexity?
- High: Causes bugs, slows delivery, new engineers can't work on it
- Medium: Takes longer than it should but doesn't cause incidents
- Low: Annoying but manageable
-
What's the remediation strategy?
- Split: Break a large file into smaller, focused modules
- Simplify: Reduce complexity through clearer abstractions
- Test: Add tests to make changes safer (doesn't reduce complexity but reduces risk)
- Rewrite: Complete replacement (last resort)
The Split Strategy
Most hotspots are hotspots because they have too many responsibilities. A 800-line file is a magnet for changes because it owns too many things.
// BEFORE: One massive file handling everything payment-related
// src/payments/processor.ts (842 lines)
// - Payment validation
// - Gateway communication
// - Retry logic
// - Refund processing
// - Receipt generation
// - Webhook handling
// AFTER: Split by responsibility
// src/payments/validator.ts (120 lines)
// src/payments/gateway.ts (180 lines)
// src/payments/retry.ts (90 lines)
// src/payments/refunds.ts (140 lines)
// src/payments/receipts.ts (110 lines)
// src/payments/webhooks.ts (130 lines)
// src/payments/processor.ts (70 lines) - orchestration onlyAfter splitting, changes to refund logic don't touch gateway code. The blast radius of each change shrinks dramatically.
The Testing Strategy
Sometimes you can't refactor a hotspot right away. In that case, wrap it in tests to make changes safer.
// Integration tests for the payments processor
// These don't fix the complexity, but they catch regressions
describe('PaymentProcessor', () => {
it('processes a valid credit card payment', async () => {
const result = await processor.charge({
amount: 5000,
currency: 'USD',
cardToken: 'tok_visa',
});
expect(result.status).toBe('succeeded');
expect(result.chargeId).toBeDefined();
});
it('retries on gateway timeout', async () => {
gateway.simulateTimeout(1); // fail first attempt
const result = await processor.charge({ /* ... */ });
expect(result.status).toBe('succeeded');
expect(gateway.attemptCount).toBe(2);
});
it('does not retry on card declined', async () => {
gateway.simulateDecline();
const result = await processor.charge({ /* ... */ });
expect(result.status).toBe('failed');
expect(gateway.attemptCount).toBe(1); // no retry
});
});Tracking Hotspot Health Over Time
Hotspot analysis isn't a one-time activity. Track your top hotspots monthly.
HOTSPOT HEALTH TRACKER - March 2026
File | Feb Changes | Mar Changes | Trend | Status
payments/processor.ts | 12 | 5 | DOWN | Improving (split completed)
api/routes/orders.ts | 11 | 9 | DOWN | Monitoring
auth/session-manager.ts | 8 | 12 | UP | Investigate
When a hotspot's change frequency drops after remediation, you know the fix worked. When a previously cold file starts heating up, catch it early before it becomes the next crisis.
The Contrarian Take
I was wrong about code quality tools for years. I thought wall-to-wall static analysis was the answer to code health. Run SonarQube on everything, fix everything it flags, and you'll have a great codebase.
That's backwards. Fixing a code smell in a file that hasn't been modified in 2 years is pure waste. The code works, nobody needs to change it, and your time is better spent elsewhere. Hotspot analysis flips the script: instead of measuring code quality everywhere and trying to improve it everywhere, measure activity patterns and improve quality only where it matters.
It's the difference between cleaning your entire house every day and cleaning the kitchen. Both are "clean" strategies. One respects reality.
Focus on the 14 files that cause 73% of your bugs. Ignore the 2,386 files that are fine. That's how you make a dent.
$ ls ./related
Explore by topic