All five functions at the top of grafana/k6’s risk table originate from a single file: internal/js/modules/k6/browser/tests/static/react-dom.development.js. The highest scorer, diffHydratedProperties, carries an activity-weighted risk score of 20.76, combines a cyclomatic complexity of 96 with nesting 13 levels deep, and has been touched twice in the last 30 days — that combination of structural mass and recent activity puts it firmly in the “fire” quadrant, meaning it is a live regression risk right now, not a cleanup item for next quarter. k6 is Grafana’s open-source load-testing engine; across its 5,605 analysed functions, 556 are rated critical and 1,798 fall into the fire quadrant, so the risk pool is large — but the concentration at the top is striking.
The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).
Top 5 Hotspots
| Function | File | Risk | CC | ND | FO |
|---|---|---|---|---|---|
diffHydratedProperties | internal/js/modules/k6/browser/tests/static/react-dom.development.js | 20.8 | 96 | 13 | 31 |
completeWork | internal/js/modules/k6/browser/tests/static/react-dom.development.js | 20.1 | 125 | 7 | 43 |
diffProperties | internal/js/modules/k6/browser/tests/static/react-dom.development.js | 20.1 | 66 | 8 | 17 |
commitDeletionEffectsOnFiber | internal/js/modules/k6/browser/tests/static/react-dom.development.js | 20.0 | 43 | 9 | 13 |
describeNativeComponentFrame | internal/js/modules/k6/browser/tests/static/react-dom.development.js | 19.9 | 45 | 8 | 16 |
Large Repo Analysis
k6 is a large repository. To stay within memory constraints, this analysis used hybrid touch mode: structural complexity — CC, ND, FO — is measured precisely for every function. Git activity is tracked at the function level (via git log -L) only for files with 5 or more commits in the last 30 days; other files use a file-level approximation. Rankings therefore surface functions that are both structurally complex and in the most actively-changing parts of the codebase. Dormant code with high structural complexity will rank lower than it would under a full per-function analysis — to surface it, run hotspots analyze . --per-function-touches on a machine with sufficient memory.
Codemod / Tooling Files in Results
All five top hotspots live in internal/js/modules/k6/browser/tests/static/react-dom.development.js. This is a bundled development build of Facebook’s React DOM library, included as a static asset for the browser module’s integration tests. Its extreme complexity scores — cyclomatic complexity up to 125, nesting up to 13 levels — are a product of React’s internal architecture, not of k6’s own engineering. To exclude it from future Hotspots analysis, add the following to your .hotspotsrc.json: { "exclude": ["internal/js/modules/k6/browser/tests/static/"] }. If any local modifications have been made to this file, those changes should be extracted before excluding the path.
Where the risk lives
Before getting into individual functions, the distribution is worth a look.
5,605 functions analyzed
Every function in the repository sits in either fire or watch — there are no debt-quadrant or ok-quadrant functions at all. That means nothing is both complex and dormant; wherever structural weight exists, there is also recent commit activity. The implication is that the usual “clean up the old stuff nobody touches” argument doesn’t apply here. The complex code is being actively changed.
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.Deeply Nested×5Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.Exit Heavy×5Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.God Function×5God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.Long Function×5Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.
Every top-five function carries all five antipattern flags simultaneously: complex branching, deep nesting, exit-heavy control flow, god-function coupling, and raw length. That’s not a coincidence — it reflects the nature of a large unmodified React development build bundled as a static test fixture. The patterns are consistent and severe.
Top 5 analysis
diffHydratedProperties — react-dom.development.js
diffHydratedProperties is responsible for reconciling DOM element properties during React’s server-side hydration pass. From the source excerpt, it opens with a large switch on the HTML tag name — dialog, iframe, video, audio, input, select, textarea, and more — attaching non-delegated event listeners for each element type before going on to compare the full property set of the incoming props against what the DOM already holds.
A cyclomatic complexity of 96 means there are 96 independent execution paths through this function. Every tag branch, every property comparison, every conditional listener attachment is a distinct path that could behave differently. Nesting reaches 13 levels deep — that’s the kind of depth where even tracing a single path by eye requires holding a stack in working memory. Fan-out of 31 means the function directly invokes 31 other functions, so a change to any one of those callees can produce a visible behaviour change here. The exit-heavy flag compounds this: multiple early-return and break paths mean test coverage requires hitting each exit independently.
Every recorded commit to this file has been tagged as a bug fix. That’s historical context, not proof of a current defect, but it does suggest this code has needed correction before.
My concrete recommendation: if this file is genuinely a vendored copy of React’s development build, exclude it from analysis (see the vendor note below) and stop treating it as first-party code. If it has been locally modified, isolate those modifications into a thin wrapper or override layer so the custom logic is visible and testable independently of the upstream bundle.
completeWork — react-dom.development.js
completeWork is React’s fiber completion phase — it runs after a fiber’s children have been processed and is responsible for wrapping up work for every possible fiber type: function components, class components, host roots, portals, suspense boundaries, and more. The source excerpt shows the function opens with a switch on workInProgress.tag and then branches deeply inside each case to handle hydration state, context providers, flags like ForceClientRender, and dehydration recovery.
At CC 125, this is the most complex function in the top five — 125 independent paths. Fan-out of 43 is the highest across all five hotspots, meaning this single function directly calls 43 distinct functions. A change to anything in that call graph can produce an unexpected effect here, and with 2 touches in the last 30 days, that call graph is live. The nesting of 7, while lower than diffHydratedProperties, still crosses the threshold where reasoning about the full interaction of conditions requires careful attention.
The same recommendation applies: if this is an unmodified upstream React build, it should be excluded from analysis. If it cannot be excluded because the file is being modified, the immediate action is to document exactly which parts differ from upstream so reviewers know where to focus.
diffProperties — react-dom.development.js
diffProperties computes the update payload for a DOM element by comparing the previous and next prop sets. The source excerpt shows it starts with a tag-based switch to normalise props for input, select, and textarea elements, then iterates over the previous props to find deletions and the next props to find additions or changes. Inside those iteration loops, it branches on special prop names — STYLE, DANGEROUSLY_SET_INNER_HTML, CHILDREN, AUTOFOCUS, event listener registrations — each with its own update logic.
A CC of 66 represents a large number of cases to test exhaustively. Nesting at 8 levels means the style-diffing loop inside the property-iteration loop inside the tag-switch produces deeply stacked conditionals. Fan-out of 17 means the function calls out broadly, including style utilities, event registration helpers, and validation functions. The exit-heavy pattern here — multiple continue statements inside the loop, plus conditional early assignment of updatePayload — means partial coverage is easy to achieve accidentally and full coverage requires deliberate scenario construction.
If this file is being kept in-tree intentionally (for example, to test the browser module’s interaction with a specific React version), pinning the version in a dependency manifest and importing the unbuilt source would be cleaner than maintaining a checked-in .development.js bundle.
commitDeletionEffectsOnFiber — react-dom.development.js
commitDeletionEffectsOnFiber handles the cleanup side of React’s commit phase — walking the fiber tree and applying unmount effects for each deleted node type. The source excerpt shows an outer switch on deletedFiber.tag with cases for host components, host text, dehydrated fragments, portals, function components, forwarded refs, and memo components. Each case manipulates a shared mutable stack (hostParent, hostParentIsContainer) before recursing into the subtree, then restores those values on the way back up.
The nesting of 9 is the second deepest in the top five. The stack-save-recurse-restore pattern visible in the excerpt is a common source of correctness bugs when the function is modified: a new case that forgets to restore state, or a fall-through between cases (there’s already an eslint-disable-next-line-no-fallthrough comment in the excerpt), can leave the traversal stack in a corrupt state. With 2 touches in the last 30 days, this is a live regression risk, not a theoretical one.
Fan-out of 13, while lower than the other entries, still means 13 functions must behave correctly in concert. The actionable step here is the same as for the rest of the file, but if any part of this function has been locally patched, those patches should be extracted, documented, and tested in isolation before the file is excluded from future analysis.
describeNativeComponentFrame — react-dom.development.js
describeNativeComponentFrame generates a stack-frame string for a React component — used in development-mode error messages to show which component caused a problem. The source excerpt shows the function deliberately triggers controlled exceptions using Reflect.construct or fn.call with a Fake constructor that throws, then compares the thrown error’s stack trace against a control stack to isolate the component’s frame. It branches on whether construct mode is requested, whether Reflect is available, and handles several nested try/catch paths.
A CC of 45 for what is nominally a diagnostic utility is high. The nesting of 8 is driven by the layered try/catch/finally structure — exception-based control flow inherently adds branches that static analysis counts. Fan-out of 16 includes dispatcher manipulation (ReactCurrentDispatcher.current), log suppression, and cache lookups, giving the function a broad reach for something that produces a string. The exit-heavy flag reflects the multiple early-return paths (return '' on guard checks, return frame on cache hit) and the exception-path exits.
This function’s complexity is arguably intrinsic to what it does — deliberately triggering and catching errors to inspect stack traces is not a simple task. That makes it a good candidate for an integration test that validates the output format, rather than a refactoring target.
A note on the Go-native hotspots
Beyond the top five, several Go functions from the actual k6 codebase are worth noting for context — applySlowMo in browser/common/hooks.go, mapRequestEvent in browser/browser/request_mapping.go, and mapResponseEvent in browser/browser/response_mapping.go — all in the moderate/watch band with low structural complexity and 2 touches in the last 30 days. None of these warrant refactoring attention now, but mapRequestEvent and mapResponseEvent are worth monitoring: event-mapping functions in a browser automation context tend to grow as new event types are added, and watching their complexity trend over time is worthwhile. The throw function in internal/js/modules/k6/experimental/streams/errors.go carries the experimental_ path signal — an actively-touched error-handling utility in an experimental module is something I’d keep an eye on as that API stabilises.
Codebase Risk Distribution
All five top hotspots share the same structural patterns (complex_branching, deeply_nested, exit_heavy, god_function, long_function), which is typical of the highest-risk functions in any large codebase — they accumulate every structural signal on the way to the top. More useful context is how the risk is distributed across all 5,605 analyzed functions:
| Band | Functions |
|---|---|
| Critical | 556 |
| High | 1,242 |
| Moderate | 2,693 |
| Low | 1,114 |
Hotspot patterns belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.
See more analyses with these patterns: complex_branching, deeply_nested, exit_heavy, god_function, long_function.
Reproduce This Analysis
git clone https://github.com/grafana/k6
cd k6
git checkout 53b5727d893d30a5c73e5b1a9891283786e0ec44
hotspots analyze . --mode snapshot --explain-patterns --force --hybrid-touches 5
To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.
I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →