codeintelligently
Back to posts
Technical Debt Intelligence

Code Hotspots: Finding the 20% of Code Causing 80% of Problems

Vaibhav Verma
7 min read
technical-debtcode-qualitycode-analysisgitrefactoringengineering-productivity

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:

  1. Change frequency: How often is this code modified?
  2. 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

bash
# 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 -20

This 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.

bash
# Lines of code for your most-changed files
wc -l src/payments/processor.ts src/api/routes/orders.ts src/auth/session-manager.ts

For 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.

bash
# Using Code Maat to find hotspots
java -jar code-maat.jar -l git_log.txt -c git -a revisions

What 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:

  1. 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)
  2. 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
  3. 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.

typescript
// 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 only

After 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.

typescript
// 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