Static analysis vs production data
Know which fallow findings you can act on from the source alone and which need production evidence. Static analysis shows what is connected, production data shows what actually ran, and fallow health combines both.
Use this page to decide when static analysis is enough and when you need runtime data. Fallow answers two questions about your codebase:
- Static analysis answers "what is connected to what?".
- Production data answers "what actually ran?".
You need both. One layer does not replace the other, and fallow health combines them.
What static intelligence sees
Static analysis builds a module graph from your source. The graph has every file, import, export, and re-export chain, plus each dynamic import() that fallow can resolve. From that graph, fallow finds:
- Reachability. Which files are reachable from an entry point, and which are not.
- Exports with zero importers. Public API that no file uses.
- Import cycles. Short and long cycles, with no depth cap.
- Duplicated blocks. Structural clones across modules, also clones with renamed identifiers.
- Complexity hotspots. Cyclomatic and cognitive complexity per function, maintainability scores, and correlation with churn.
- Architecture boundaries. Layered, hexagonal, feature-sliced, or custom zones. Fallow follows re-export chains across zones.
- Feature flag definitions. Where you declare flags, where the code branches on them, and which branches exist.
Static analysis reports these examples with confidence:
- An
export function computeTaxthat no file imports, directly or through a barrel. - A file at
src/server/jobs/worker.tsthat no entry point, script, or test reaches. - A tight cycle:
a.ts -> b.ts -> c.ts -> a.ts. - A 60-LOC function with cyclomatic 28 and cognitive 34, in a file that changed 40 times in the last 90 days.
These results are deterministic. You can trace each finding back to a path through the graph.
What static intelligence cannot see
The graph is a model of the code. It is not a recording of what the program did. Static analysis cannot see:
- Dynamic dispatch. Handlers registered by string key, plugins loaded from an array, and methods called through
obj[name](). - Conditional imports.
if (process.env.FOO) { await import("./bar") }may or may not execute in production. - Feature flag branches. Static analysis sees both branches. It cannot tell you which branch users hit this month, and function-level runtime coverage cannot either.
- Traffic-weighted importance. In the graph, a 5-line helper called 200 million times a day looks the same as a 5-line helper called once at startup.
- Reachable but unused code. A function that is imported and could run, but had no calls in 30 days.
Static reachability tells you that code could run. It does not tell you that the code did run.
What production data adds
Production data adds evidence. It records which code executed, then merges that evidence into the analyses you already run:
- Hot functions. The functions that handle production traffic. A change there needs extra review.
- Cold functions. Reachable in the graph, but with no calls in production. These are high-confidence candidates for removal.
- Runtime-weighted health. High complexity is a signal. High complexity on a hot path is an alarm.
Runtime coverage collects the production data. You pass it to the CLI with one flag: fallow health --runtime-coverage ./.fallow/runtime.json. To set up collection, see the runtime coverage analysis page.
Where they meet: fallow health
fallow health merges static findings and runtime evidence into one result. Examples:
- A file has cyclomatic complexity 24, a dead code ratio of 0.7, and zero runtime hits over 30 days. Static analysis alone calls it complex and partly unused. Runtime data alone calls it cold. With both, fallow calls it a confident deletion candidate.
- In this PR, a function went from cognitive 12 to cognitive 26. It is on a hot path with 40M calls a day. Static analysis alone flags the complexity increase. Runtime data raises the priority of the review.
Runtime evidence refines static findings. It never overrides them. A function with zero runtime hits is only a candidate when static analysis says it is reachable. An unreferenced export is already dead, and runtime data does not change that.
Mental model
| Question | Answered by |
|---|---|
| What is connected to what? | Static |
| What imports this file? | Static |
| What files nothing imports? | Static |
| What code changed in this PR? | Static (graph + diff) |
| What actually ran in production? | Runtime |
| Which functions are hot? | Runtime |
| Did this branch execute this month? | Runtime |
| Is this code safe to delete? | Both |
| Does this PR touch hot code? | Both |
Free vs paid layer
Fallow, the open-source CLI, is free (MIT). You can run every static analysis on any project with npx fallow, with no license.
Fallow Cloud is the paid product: Fallow plus production data. Runtime coverage is the feature that collects the production data. These rules apply:
- A single local capture is free and needs no license.
- Continuous or multi-capture monitoring needs a license. A 30-day trial is available.
- Runtime data from Fallow Cloud needs a
FALLOW_API_KEYfor an organization on the Team tier. It does not use the local license.
To start a trial or activate a license, see fallow license. For pricing and tiers, see fallow.tools/pricing.
Start with static analysis. It is free, and it already finds most of the dead code in a typical codebase. Add runtime data when you want hot-path review, runtime-weighted health, or deletion confidence from production evidence.