Static vs Dynamic Evidence in Code Review
Quick answer: Static evidence comes from tools that read the code without running it, such as typecheck, lint, and build, plus references to APIs that do not exist. Dynamic evidence comes from actually executing something, such as tests, coverage, and smoke runs. They catch different defects, have different blind spots, and neither one by itself proves the code is correct.
Where each kind of evidence comes from
The review classifies evidence so you can tell a reasoning step from a measured result.
| Dimension | Static evidence | Dynamic evidence |
|---|---|---|
| How it is produced | Read and analyze code, no execution | Run tests, coverage, or smoke calls |
| Typical signals | Typecheck, lint, build, hallucinated API | Test pass/fail, coverage, smoke behavior |
| Speed | Fast, repeatable, no running service | Slower, needs a runnable target |
| Main strength | Structure, types, references, drift at rest | Behavior that only appears when code runs |
| Main blind spot | Cannot see runtime behavior | Only covers paths the executed run reaches |
Static evidence is cheap to rerun and points at structural problems: a type that does not line up, a lint rule that fails, a build that breaks, or a call to an API that is not in the resolved inventory. Dynamic evidence answers a different question: what happens when the code actually runs.
What static evidence catches well
Static analysis resolves definitions, callers, and imports, which makes it strong at problems that live in the structure of the code.
- Type mismatches and references to symbols that do not exist.
- Build and lint failures that block a clean delivery.
- Calls to a hallucinated API, where the model named an endpoint the real inventory does not contain.
- Structure signals such as heavily referenced files, temporary names, and identical content.
It is deterministic and does not depend on a running service, so it gives a stable baseline. The limit is that it never sees the program execute. Dynamic loading, framework entry points, and external consumers can be missed, and no incoming reference does not mean a file is safe to delete.
What dynamic evidence adds
Dynamic evidence comes from a real run, so it surfaces behavior that static reading cannot reach. A test that fails, a coverage report that shows an unexercised branch, or a smoke call that returns an unexpected status all depend on execution.
The trade-off is coverage. A dynamic run only reports the paths that the executed tests or smoke calls actually reach. If the test suite never enters a branch, you get no evidence about that branch, which is not the same as evidence that it works. Run checks execute detected project commands explicitly, and when a script is missing the result is unverified, not passed.
Why the two belong together
The strongest review reads both, because they fail in ways the other cannot see. A change can pass typecheck and lint yet fail a test at runtime, and a test can pass while a build is broken or a referenced API does not exist. Reading one class and assuming the other is fine is how defects slip through.
Contract evidence sits alongside these, covering schema and type drift against the spec, while any model-only note stays an AI hypothesis. The verdict is deterministic with no model involved: failing evidence fails, partial verification is partial, and a lack of verified evidence is inconclusive because an empty result is not a pass.
Powerduck keeps each evidence item tied to a reproducible command and raw output, so you can see whether a note came from a static command or a real run and verify it yourself.
Frequently asked questions
Is static evidence enough if the build passes? No. A passing build and typecheck do not test runtime behavior. A defect can survive static checks and only appear under a test or smoke run.
Do passing tests prove the code is correct? No. Tests cover only the paths they reach, and a missing script is reported as unverified rather than passed. Untested branches carry no evidence.
Which is faster to rerun? Static checks are generally faster because they do not need a running target, while dynamic evidence requires a runnable service or test suite.
The bottom line
Static evidence tells you whether the code is structurally sound without running it, and dynamic evidence tells you how it behaves when it executes. They are complementary, not interchangeable: static analysis has no view of runtime behavior, and dynamic runs only cover the paths they reach. Keep both, treat missing evidence as inconclusive, and remember that neither class turns a review into a proof of correctness. Read more in the AI code review guide.