Hotspot Analysis
Pattern: A named solution to a recurring problem.
“The idea is to prioritize our technical debt based on the amount of recent development activity.” — Adam Tornhill
Read the version-control history for code that is both hard to work with and constantly changing, and for files that keep changing together, so refactoring effort goes where it pays off.
Also known as: Behavioral Code Analysis, Change Coupling Analysis, Logical Coupling, Temporal Coupling
Every codebase has a file that seems to be in every pull request. The order service, the shared types module, the settings file with a thousand lines of branches. Everyone knows it’s painful. Nobody can say whether it’s worse than the twenty other files people complain about, because the code doesn’t record how often anyone has to open it. The commit log does. Each commit is a small record of which files someone had to change together to make one thing work, and enough of them show you where the system pushes back.
Understand This First
• Version Control — the commit history this pattern treats as evidence.
• Coupling — the property that change coupling measures from behavior instead of from imports.
• Smell (Code Smell) — the vocabulary you use once history has told you where to look.
Context
This is a heuristic for deciding where to spend refactoring effort in a codebase too large to fix all at once, which describes nearly every codebase older than a year. Static analysis tells you what the code looks like today: which functions are long, which modules import which. It can’t tell you which parts the team actually works in. A tangled module nobody has touched in three years costs almost nothing. A mildly messy one that changes every week charges a small fee on every change, indefinitely. That fee is the part of technical debt you actually pay.
Agents change the calculation twice over. Large refactorings are cheaper to carry out than they’ve ever been, so the hard part moves from doing the work to choosing it. And agents pay for a hot, tangled file more often than people do. They re-read it into the context window on every task that touches it, they miss the sibling file that always has to change with it, and parallel agents collide on it in merge conflicts.
Problem
You can’t refactor everything, and the code that looks worst isn’t necessarily the code that costs most. How do you find the few places where structural work would make most future changes cheaper, using evidence rather than whoever complains loudest?
Forces
• Complexity alone misranks. The ugliest code is often stable, and stable code rarely needs rescuing.
• Change frequency alone misranks too. A changelog, a version file, or a translation catalog changes constantly and is perfectly healthy.
• Some real dependencies are invisible in the code’s structure. Two modules with no import between them can still be bound by a shared assumption that forces them to change together.
• Commit history is noisy. Bots, mass reformatting, vendored code, and renames all leave records that look like work.
• A ranked list is cheap to produce and easy to over-trust, especially once it’s handed to an agent that will act on it.
Solution
Mine the commit history for two signals, weigh the first against a complexity measure, and treat the result as a ranked list of places to go and read, never as a list of verdicts.
The first signal is the hotspot: a file, or a function within one, that is both complex and frequently changed. Count commits per file over a recent window, measure each file’s size or complexity, and look at the files that score high on both. Lines of code are a crude but serviceable complexity proxy. Adam Tornhill, who did most to popularize the method, also uses indentation depth, since it works in any language and needs no parser. The distribution is usually steep: a small share of files absorbs a large share of the changes, and a few of those are also the hardest to read.
The frequency half is a one-liner in Git:
# Commits per file over the last six months, most-changed first
git log --since="6 months ago" --no-merges --format= --name-only \
| grep -v '^$' \
| sort | uniq -c | sort -rn | head -20
That command counts every commit equally, including the ones a bot made and the ones that only touched a lockfile. Treat its output as raw material for the filtering below, not as an answer.
The second signal is change coupling, also called logical or temporal coupling: pairs of files that keep appearing in the same commits. Measure it as the share of one file’s commits that also touch the other. A test file coupled to its implementation at 90% is healthy. A billing module coupled at 60% to an email template in a different service is a hidden dependency: the import graph doesn’t show it, but everyone who has changed one has learned to change the other. This is the history-side view of the Shotgun Surgery smell, where one conceptual change has to be spread across many files.
Clean the input before you trust the output. A few rules carry most of the weight:
1. Use a recent window. Old history describes problems the team may already have fixed. Six to twelve months suits most teams. Tornhill’s early advice for very active codebases was two or three months.
2. Filter the noise. Exclude commits from bots and dependency-update services, mass reformats, generated files, lockfiles, and vendored code. Cap the commit size that counts toward coupling, because a commit that renames 200 files couples everything to everything. Code Maat, Tornhill’s open-source analysis tool, leaves commits that touch more than 30 files out of its coupling analysis by default.
3. Set floors. Drop files with only a handful of commits, and coupling pairs below roughly 30%, so the ranking isn’t built from coincidences.
4. Expect healthy coupling. Implementation and test, schema and migration, an interface and its single implementation are supposed to move together. Note them and move on.
Then read. For each of the top few hotspots, open the file and the last couple of dozen commits that touched it, and ask why it keeps changing. The answer is the diagnosis, and it varies. The file may mix several unrelated responsibilities, a cohesion problem. It may be a God Object that every feature has to pass through. It may hold configuration that should be data. Or it may be where the product is growing, and it’s fine. Only after the diagnosis does a refactoring plan make sense.
The work splits cleanly between script, agent, and person. Counting commits is deterministic bookkeeping, so run it as a script; don’t ask a model to eyeball a log. Reading a hot file alongside its history and explaining why it churns is work an agent does competently, with a person checking the explanation. The refactoring can then go to an agent as a bounded task, with the coupling data as its acceptance test: these two files should be changeable independently, and the next quarter’s analysis should show it.
How It Plays Out
A freight-logistics company sets aside two weeks of agent time to “pay down technical debt” in its Rails monolith. Instead of letting the agent choose, the tech lead runs a hotspot script over nine months of history. Of about 2,400 files, 31 account for half the commits. Near the top sits a 1,400-line rate calculator, and the coupling report shows it changing with a customs-paperwork module in another package in 64% of its commits, with no reference between them. The lead hands the agent both files, their last 30 commits, and the report, and asks for a diagnosis only. The agent finds that both files hard-code the same table of carrier surcharges, so every pricing update edits both. The fix is small: move the table to one place both read. Three months later the pair’s coupling is under 10%.
A platform team runs the same kind of analysis and finds its top five hotspots are package.json, three lockfiles, and CHANGELOG.md. Nearly all of those commits come from the dependency-update bot and the release script. That’s common: a 2026 study of 91 actively developed GitHub projects found that automated accounts made 74% of the edits to their line-level hotspots. Once the team filters out automated authors and generated files, the real leader turns out to be a feature-flag registry that a dozen teams edit by hand. That’s a contention problem rather than a complexity one, and the fix is splitting the registry by owning team.
A newly hired engineer wants to rewrite a 3,000-line tax module, easily the ugliest code in the repository. The history shows four commits in two years, each a yearly rate update. The engineer leaves it alone and spends the time on a mid-sized checkout controller that changed 90 times in the same period.
Example Prompt: “Here are the five most-changed files from the last six months, each with its 20 most recent commits, plus the coupling report. For each file, explain in two or three sentences why it keeps changing, and say whether a refactoring would reduce future changes or only move them around. Don’t edit any code yet.”
Consequences
Benefits. Refactoring effort lands where changes actually happen, so each improvement pays back on every future change to that code. Prioritization rests on evidence the whole team can inspect, which ends a lot of arguments about whose least favorite module matters most. The method is language-neutral and needs nothing but a repository with history. Change coupling exposes dependencies that architecture diagrams and import graphs miss, and it predicts the real write set of a change, which helps you scope agent tasks and decide which ones are safe to run in parallel. And it gives an agent’s refactoring a measurable outcome: rerun the analysis later and see whether the coupling broke.
Liabilities. The analysis is only as good as the commit history. Squash-merging turns each pull request into one commit, which inflates coupling to the size of the PR. Very small commits deflate it. Renames break a file’s history unless the tooling follows them. New code has no history at all, so the method is blind to fresh problems. A hotspot also tracks the roadmap as much as the design: a hot file may be nothing more than the place where the product is growing, which is why reading the code is not optional.
Like any metric, a ranking invites gaming. If teams are judged by their hotspot scores, they’ll split commits or files to look better without changing anything that matters. Per-author views of the same history (who changes what, how knowledge is spread) are useful for spotting single points of failure, and corrosive when they turn into a performance metric for individuals.
Agents add a new kind of noise. An agent that commits ten times per task inflates a file’s change count against a person who commits once. As agent-authored commits grow as a share of history, decide deliberately whether to count them, weight them, or measure by merged pull request instead.
Related Articles
Depends on: Version Control — The commit history is the raw material, so the analysis can only be as honest as the history it reads.
Detects: Coupling — Change coupling finds dependencies the import graph doesn't show by measuring which files actually change together.
Detects: God Object — A God Object shows up in history as a file near the top of the change ranking, coupled to much of the system.
Extends: Codebase Map — A codebase map shows the structure as it stands; hotspot and coupling data add how that structure has been changing.
Informs: Architecture Fitness Function — Once you've broken a coupling you care about, a fitness function can fail the build if the two files start moving together again.
Informs: Cohesion — A hot file that changes for many unrelated reasons is usually a cohesion problem that can be split along those reasons.
Informs: Conway's Law — Files that keep changing together across team boundaries mark places where the organization and the architecture disagree.
Informs: Smell (AI Smell) — Repeated agent struggle in a module is the live version of what history records as a hotspot, so each can confirm the other.
Informs: Task Decomposition — Coupling data predicts which files a change will really touch, so a task can be scoped before an agent starts on it.
Measures: Technical Debt — Ranking debt by recent change separates the debt you pay interest on every week from the debt nobody touches.
Specializes: Metric — Change frequency and coupling degree are metrics and carry every metric's risk of being misread or gamed.
Upstream of: Refactor — Hotspot analysis chooses where to refactor; the refactoring follows only after someone has read the code and found the cause.
Uses: Smell (Code Smell) — Once history nominates a file, you read it for named smells to learn why it keeps changing.
Sources
• Harald Gall, Karin Hajek, and Mehdi Jazayeri introduced logical coupling in “Detection of Logical Coupling Based on Product Release History” (International Conference on Software Maintenance, 1998), deriving hidden dependencies between modules from how they changed together across 20 releases of a large telecommunications switching system.
• Thomas Zimmermann, Peter Weißgerber, Stephan Diehl, and Andreas Zeller showed in “Mining Version Histories to Guide Software Changes” (ICSE 2004) that co-change mined from version history can tell a developer which other files a change is likely to need.
• Nachiappan Nagappan and Thomas Ball’s “Use of Relative Code Churn Measures to Predict System Defect Density” (ICSE 2005) gave early industrial evidence that how much code changes, measured relative to its size, predicts where defects concentrate.
• Adam Tornhill’s “Code as a Crime Scene” (Overload 117, ACCU, October 2013) set out hotspots as a way to prioritize technical debt by recent activity, along with logical-coupling thresholds and the short recent window; it is the source of the epigraph.
• Adam Tornhill’s Your Code as a Crime Scene (Pragmatic Bookshelf; second edition 2024) is the book-length treatment of hotspots, change coupling, and the social side of code history, and his Software Design X-Rays (Pragmatic Bookshelf, 2018) extends the method to function-level hotspots and change coupling across multiple repositories and services.
• Tornhill’s Code Maat is the open-source command-line tool that accompanies both books and implements the frequency, coupling, and authorship analyses described here.
• Saleha Muzammil, Mughees Ur Rehman, Zoe Kotti, and Diomidis Spinellis’s “Source Code Hotspots: A Diagnostic Method for Quality Issues” (2026) catalogs 15 recurring line-level hotspot patterns across 91 GitHub projects and documents how much of hotspot activity comes from automated accounts.