On this page
- What is code churn?
- How to measure code churn with git
- Relative churn
- Does code churn predict defects?
- Churn vs complexity: which files are hotspots?
- Why churn is a bad productivity metric
- Which churn is healthy and which is a warning?
- How repowise computes churn, hotspots and bug magnets
- Pitfalls when measuring churn
- FAQ
Code churn is the amount of code that is added, modified or deleted in a file, a module or a whole system over a period of time. You measure it from version control: git log --numstat gives added and deleted lines per file per commit, and summing them over a window gives churn. High churn on its own only says that work is happening there; high churn on code that is also complex or has been fixed repeatedly is a strong signal of risk.
What is code churn?
Code churn is lines added plus lines deleted (a modified line counts as one of each) in a file or system over a time window, usually 30 to 180 days. Some tools count only the commits that touched a file, which is a related but different number. It helps to name the variants, because they behave differently:
| Variant | Definition | Good for |
|---|---|---|
| Commit churn | Number of commits that touched the file in the window | Spotting where work lands; robust to one huge commit |
| Line churn | Lines added plus lines deleted in the window | Sizing how much changed |
| Relative churn | Line churn divided by the file's current size | Comparing files of different sizes; the strongest defect predictor in the research below |
| Author churn | Number of distinct authors in the window | Coordination cost and ownership risk |
Scroll the table sideways to see every column.
A related term is code rework or "short-term churn": code rewritten shortly after it was first written. It is a narrower concept, and tools that report "churn" as a percentage of recent lines are usually reporting that.
How to measure code churn with git
git log --numstat prints added and deleted lines for every file in every commit, and a short awk script turns that into churn per file. This runs on any repository, with nothing installed.
git log --since="180 days ago" --no-merges --numstat --pretty=format: -- pydantic \
| awk 'NF==3 && $1!="-" {c[$3]++; a[$3]+=$1; d[$3]+=$2}
END {for (f in c) printf "%d\t%d\t%d\t%s\n", c[f], a[f], d[f], f}' \
| sort -rn | head -8
The columns are commits, lines added, lines deleted, path. Here is the output on pydantic at commit 0a7887305 (29 October 2026), over the pydantic/ package:
27 505 141 pydantic/json_schema.py
27 421 359 pydantic/_internal/_generate_schema.py
17 208 99 pydantic/main.py
11 95 88 pydantic/_internal/_validators.py
11 62 81 pydantic/type_adapter.py
11 158 46 pydantic/types.py
10 69 12 pydantic/mypy.py
10 111 84 pydantic/experimental/pipeline.py
Three details matter. --no-merges stops a merge commit from counting the same lines twice. The $1!="-" test skips binary files, which git reports with dashes. And the -- pydantic pathspec scopes the count to production code, so lockfiles, vendored code and changelogs, which churn without carrying risk, stay out.
Relative churn
Relative churn divides a file's churn by its size, so a 40-line file rewritten twice outranks a 4,000-line file edited twice. Add the current line count from the working tree:
git log --since="180 days ago" --no-merges --numstat --pretty=format: -- pydantic \
| awk 'NF==3 && $1!="-" {c[$3]++; ch[$3]+=$1+$2} END {for (f in c) print c[f], ch[f], f}' \
| while read c ch f; do [ -f "$f" ] && echo "$c $ch $(wc -l < "$f") $f"; done \
| awk '{printf "%d\t%d\t%d\t%.2f\t%s\n", $1, $2, $3, $2/$3, $4}' \
| sort -k4 -rn | head -5
On the same pydantic checkout the top rows are:
4 280 49 5.71 pydantic/_internal/_schema_gather.py
1 128 128 1.00 pydantic/_internal/_type_refs.py
1 28 29 0.97 pydantic/experimental/missing_sentinel.py
7 220 502 0.44 pydantic/_internal/_typing_extra.py
3 68 156 0.44 pydantic/_internal/_core_utils.py
The ranking changes completely. json_schema.py, first by commits, is no longer in the top five, and a 49-line file that was substantially rewritten four times leads. The cost is that small files with one edit now rank high, so read relative churn next to commit count (the first two columns), not instead of it.
Does code churn predict defects?
Yes, and the strongest evidence is for relative churn, not raw churn. In "Use of Relative Code Churn Measures to Predict System Defect Density" (Nagappan and Ball, ICSE 2005, Microsoft Research), the authors studied Windows Server 2003 and found that absolute churn measures are weak predictors of defect density while relative measures, which relate churn to component size and the time span of the changes, are strong ones. Their churn measures discriminated between fault-prone and not fault-prone binaries with an accuracy of 89.0 percent, per the paper's abstract.
Two later results shape how churn is used in practice.
- Change history is not the only signal. In a Windows Vista study, Nagappan, Murphy and Basili (2008) found organisational metrics, such as how many engineers touch a component and how ownership is spread, predicted failure-proneness better than churn, complexity and dependency metrics. Churn tells you where the changes are; the people data tells you whether anyone understands them.
- Churn becomes sharper when multiplied by complexity. Adam Tornhill's Your Code as a Crime Scene (Pragmatic Bookshelf, 2015; a second edition followed in 2024) popularised the hotspot: a file that is both complex and frequently changed. A complex file nobody touches is quiet debt, and a simple file edited daily is normal development.
repowise's own check on this relationship, from its change-risk documentation: against churn alone, a model that adds structural and ownership features gains only a little. Leave-one-repo-out AUC on 4,102 commits across 7 repositories is 0.772 for the model and 0.766 for churn. Churn is a strong baseline that is hard to beat by much.
Churn vs complexity: which files are hotspots?
A hotspot is a file in the top of both the churn ranking and the complexity ranking. Plot the two on axes and the files worth your time sit in the upper right. For complexity, any per-function cyclomatic complexity tool works; lizard supports many languages (pip install lizard, then lizard -w pydantic/ prints only the functions over its thresholds, cyclomatic complexity 15 by default).
| Low complexity | High complexity | |
|---|---|---|
| Low churn | Fine; leave it alone | Stable debt: ugly but quiet, fix when you must touch it |
| High churn | Active development, usually healthy | Hotspot: where bugs, merge conflicts and slow reviews concentrate |
Scroll the table sideways to see every column.
The step-by-step version of this, including bug-fix counts and ownership, is in git hotspot analysis: find the riskiest files. This page stays on churn itself.
Why churn is a bad productivity metric
Churn measures how much code moved, not how much value was delivered, so using it to rate developers punishes the wrong behaviour. Deleting a dead 2,000-line module scores as heavy churn. A mechanical rename, a formatter run, a regenerated API client and a dependency lock update all score high. A developer who solves a problem in ten lines scores lower than one who solves it in a hundred. Any metric that rewards typing more lines will get more lines.
The metric is useful at the level of files and components, as a risk signal, and misleading at the level of people. The same applies to commit counts. If someone proposes a per-developer churn dashboard, the better question is which files are getting rewritten repeatedly, and why.
Which churn is healthy and which is a warning?
Churn is healthy when it is a feature under construction and a warning when the same file keeps being edited to fix itself. Four patterns to separate:
- New feature, new file. High relative churn in the first weeks of a file's life is normal.
- Steady edits to a core file. Commit churn high, line churn low: many small changes. Often a central configuration or registry file; read it as coupling, not instability.
- Repeated rewrites. The same lines added and deleted again and again, usually with "fix" in the commit subjects. This is the pattern that predicts defects, and the reason to add bug-fix history to the count.
- Mechanical churn. Formatting, renames, generated code. Exclude it with pathspecs, or add
-wtogit logto ignore whitespace-only changes.
How repowise computes churn, hotspots and bug magnets
repowise reads the same git history, weights each commit by age, and joins churn with bug-fix history, ownership and code health. The git analysis runs inside repowise init, needs no LLM key and makes no network calls.
git clone https://github.com/pydantic/pydantic.git && cd pydantic
repowise init --yes --no-prose --no-editor-setup
repowise risk --target pydantic/json_schema.py
Real output from that run (pydantic at 0a7887305, 29 October 2026, trimmed):
pydantic/json_schema.py
hotspot 100% (stable) · 24 direct dependents · bug-prone · owned 28% by Victorien
health 1.0/10
change_entropy (critical), god_class (critical) in GenerateJsonSchema, ownership_risk (critical)
+472 / -136 lines in 90d · avg commit 25.3 lines · 40 contributor(s), bus factor 7
4 bug fix(es) in 6 months, last 17 day(s) ago
Mostly in typed_dict_schema (x2), dataclass_args_schema (x1), dataclass_schema (x1)
Co-changes with
tests/test_json_schema.py x114 (also imports)
pydantic/_internal/_generate_schema.py x65 (also imports)
The definitions behind those lines come from the bug-fix history and ownership docs:
- A hotspot is a file whose recency-weighted churn is in the top quartile of the repository, with real recent activity. Churn is weighted with a 180-day half-life, so last week's commit counts more than last year's.
- A bug fix is a commit whose subject matches fix keywords and whose diff changes production source lines. Docs-only, test-only, config-only and comment-only fixes are not counted.
- A bug magnet is a file whose age-decayed fix count reaches 3.0. The decay uses a 90-day half-life, so the flag needs about four recent fixes and goes quiet when a file settles.
- Complexity enters through the code health layer, a 0 to 10 score from deterministic detectors, with a separate hotspot health figure over hotspot files only. On this checkout it reported 6.54/10 for hotspots against 7.75/10 overall.
The same data is a per-repository dashboard on the git intelligence page, and agents read it through the get_risk MCP tool before they edit a file.
Pitfalls when measuring churn
- Shallow clones. CI checkouts often fetch limited history, which undercounts everything. Run
git fetch --unshallowfirst. - Squash merges. If every pull request is squashed into one commit, commit churn understates how often a file changed. Line churn is less affected.
- Renames.
--numstatprints a moved file assrc/{old => new}.py, which splits its history across two keys in the awk script. Add--no-renamesto count the old and new paths separately, or accept that moved files restart at zero. - One window for everything. Ninety days suits an active service; a library with quarterly releases needs more.
- Treating the list as a to-do list. The top file may be busy because it is the one place a feature registry lives. Read the fixes and the owners before refactoring.
FAQ
- What is code churn?
- Code churn is the amount of code added, modified or deleted in a file or a system over a period of time. It is measured from version control history, usually as lines added plus lines deleted, or as the number of commits touching a file, over a window such as 90 days.
- How do you calculate code churn?
- Run
git log --since="90 days ago" --no-merges --numstat --pretty=format:and sum the added and deleted columns per file. Divide by the file's current line count for relative churn. The commands in this guide print a ranked list on any repository. - Is high code churn bad?
- Not by itself. A file under active feature work should have high churn. It becomes a warning when the churn is repeated rewrites of the same lines, when the file is also complex, or when recent commits are bug fixes. Churn tells you where to look, and complexity and fix history tell you whether to worry.
- What is the difference between churn and complexity?
- Churn measures how often and how much code changes; complexity measures how hard the code is to understand, for example through cyclomatic complexity. A file high on both is a hotspot. A complex file that never changes is stable debt, and a simple file that changes constantly is normal development.
- Does churn predict bugs?
- Research says yes, with a caveat. Nagappan and Ball's 2005 study of Windows Server 2003 found that relative churn, which divides change by component size, was a strong predictor of defect density, while absolute churn was weak. Organisational factors such as how many people touch a component can predict failures better still.
- Is code churn a good developer productivity metric?
- No. Churn counts lines moved, and deletions, renames, formatting runs and generated code all raise it without adding value. It is a reasonable signal about files and components, and a misleading one about people.
- What is a good code churn rate?
- There is no universal number, because it depends on the codebase and its phase. Compare a file to its own repository: files in the top quartile of recent churn deserve attention, especially when they also have bug fixes and low health scores.



