Out of 7,667 functions analyzed in moby/moby, NewDaemon in daemon/daemon.go tops the list with an activity-weighted risk of 20.38 — a cyclomatic complexity of 198, a max nesting depth of 7, and fan-out to 143 distinct functions, combined with 5 commits touching it in just the last 30 days and a change only 6 days ago. That is not a dormant god function sitting untouched in the codebase; it is being actively edited while carrying the highest structural load I found in the repo. Docker’s engine is a large, mature codebase — 1,674 of its functions land in the critical band — so I’d start this week’s review with the daemon bootstrap path, not because it looks messy in the abstract, but because it is both complex and live.
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 |
|---|---|---|---|---|---|
NewDaemon | daemon/daemon.go | 20.4 | 198 | 7 | 143 |
delete | daemon/libnetwork/network.go | 19.1 | 39 | 3 | 37 |
Images | daemon/images/image_list.go | 18.4 | 106 | 6 | 49 |
restore | daemon/daemon.go | 18.1 | 44 | 6 | 102 |
ImageDelete | daemon/images/image_delete.go | 17.6 | 53 | 6 | 27 |
Codemod / Tooling Files in Results
One pair of context-only functions, GenerateContainerName and its sibling generateContainerNameRequestFromProto, live in extpoints/containernamegenerator/v0/protogen/wire.gen.go — a generated wire-protocol file, not hand-written application code. Generated code like this inflates function counts without reflecting real maintenance burden, so I’d exclude it going forward with { 'exclude': ['**/protogen/**', '**/wire.gen.go'] } in .hotspotsrc.json.
7,667 functions analyzed
The quadrant split tells the real story here: 222 functions sit in the ‘fire’ quadrant — complex and actively changing right now — against 3,465 in ‘debt’, meaning most of moby’s structural risk is sitting quiet rather than being actively edited. That’s a normal shape for a codebase this size, but it means prioritization has to be quadrant-aware. A function with high complexity and zero recent touches is not the same risk profile as one being edited five times in the last month.
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.God Function×10God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.Long Function×10Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.Complex Branching×8Complex Branching
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.Stale Complex×2Stale Complex
High structural complexity but untouched for a long time — structural debt that will bite whoever opens it next.Hub Function×1Hub Function
Many other functions call this one — a change here ripples widely through callers.
Across the top hotspots, every single one carries the god_function, long_function, and exit_heavy patterns — ten out of ten. That’s a consistent signature: large functions with many early returns and broad responsibility, exactly what shows up in daemon setup, image listing, and container restore paths in this codebase.
NewDaemon — daemon/daemon.go
This is the constructor that wires up a running Docker daemon: registry service, root key limits, config verification, bridge network state, resolv.conf, remapped root UID/GID, temp directory resolution, runtimes, and image store selection all happen in this one function, based on the excerpt I reviewed. A cyclomatic complexity of 198 with a nesting depth of 7 means 198 independent paths through initialization logic, and a fan-out of 143 means this function is coupled to a large share of the codebase directly — any change to one of those 143 callees is a candidate for breaking daemon startup. The external signals show 51 total commits and 7 distinct authors in the last 90 days touching this file, with a bug-fix fraction of 0.1373 — not a red flag on its own, but consistent with a function that many people are actively modifying. With 5 touches in the last 30 days and a change just 6 days ago, this is squarely a live-risk function, not a backlog item. My recommendation: extract the platform-specific temp-directory and runtime-setup blocks (the Windows/non-Windows branch and setupRuntimes sequence are clearly delineated in the excerpt) into named helper functions with their own error returns — that alone would cut both the nesting depth and the number of paths a reviewer has to hold in their head during the next PR touching this file.
delete — daemon/libnetwork/network.go
This is network teardown logic — force-delete handling, load-balancer sandbox cleanup, gossip cluster departure, and store cleanup, based on the excerpt. It carries a cyclomatic complexity of 39 and fan-out of 37, and the code itself contains a comment acknowledging the risk directly: errors before a certain point are recoverable, errors after are not, because there’s no safe way to reconstitute a load-balancer endpoint once removed. That’s a structural signature worth taking seriously on its own terms. This function has 0 touches in the last 30 days and hasn’t been changed in 320 days — it is debt, not fire. The blast radius is real (37 callees, a goto for early exit to a cleanup label) but the risk is latent: it will surface the next time someone has to touch network deletion, likely under time pressure. I’d treat this as a pre-emptive refactor candidate — split the recoverable-error checks from the point-of-no-return cleanup sequence into two functions — rather than an urgent fix.
Images — daemon/images/image_list.go
Images handles the list-images API path: filter validation, three separate WalkValues closures for the before/until/since date filters, dangling-image selection, and a per-image summary loop with container cross-referencing. A cyclomatic complexity of 106 with nesting depth 6 is high for a read-path function, and the three repeated filter-parsing closures are a clear extract-method opportunity — each follows the same shape (parse value, resolve to oldest/newest image, handle error) and could become a single parameterized helper. This function has 0 touches in the last 30 days and was last changed 122 days ago, so it sits in the debt quadrant: no one is actively editing it right now, but the next engineer who has to add a new filter type will inherit all 106 paths at once. Fan-out of 49 also means this function’s list-filtering logic isn’t isolated — it depends on image store internals and container state directly.
restore — daemon/daemon.go
restore is the container-restoration path run at daemon startup, and it’s explicitly concurrent: goroutines launched per-container, a semaphore-limited parallelism model (adjustParallelLimit, sem.Acquire/Release), a shared sync.WaitGroup, and a mutex-guarded map for tracking containers to remove. Fan-out of 102 is the second-highest in this list, and nesting depth of 6 combined with per-goroutine error handling (log-and-continue rather than propagate, based on the excerpt) means the execution paths are not just numerous but concurrent — channel and goroutine coordination bugs here wouldn’t necessarily show up as a simple return-path error, which structural metrics alone can’t fully capture. This function shares a file with NewDaemon and shows the same 51 total commits / 7 authors in 90 days at the file level, but at the function level it has 0 touches in the last 30 days and hasn’t changed in 157 days — so despite living beside the hottest function in the repo, restore itself is dormant debt right now. Given the goroutine fan-out, I’d prioritize adding concurrency-specific tests (race detector runs against the parallel restore path) before the next refactor, not just splitting the function by line count.
ImageDelete — daemon/images/image_delete.go
ImageDelete handles image removal: platform selection, reference resolution, an inline using closure to check container-image association, and conditional untagging logic based on force/prune flags and whether the reference is a digest or tag. Cyclomatic complexity of 53 and nesting depth 6 reflect the branching around forced-versus-safe deletion and canonical-versus-tag reference handling. The stale_complex pattern is notable here alongside a bug-fix fraction of 0.5 at the file level (2 total commits, 1 in the last 90 days) — a small sample, so I wouldn’t read too much into it, but it does mean half of the recorded history on this file was bug-fix related. Combined with 320 days since this function was last changed, this is tied with delete for the stalest function in the top five: high blast radius, no recent attention. If image deletion logic needs to change for any reason — new platform support, new retention policy — this function will absorb that change across all 53 paths at once.
A few functions in the context data are worth a mention without full sections: postContainersCreate (daemon/server/router/container/container_routes.go, cyclomatic complexity 121, activity risk 17.42, touched once in the last 30 days) and start in daemon/command/daemon.go (cyclomatic complexity 79, fan-out 103, activity risk 16.98, 3 touches in 30 days) are both ‘fire’ quadrant and sit just below the top five — worth watching on the next pass. ServiceSpecToGRPC in daemon/cluster/convert/service.go is also ‘fire’ with a cyclomatic complexity of 77 despite a comparatively low fan-out of 17, suggesting its complexity is concentrated in branching rather than coupling.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
exit_heavy | 10 |
god_function | 10 |
long_function | 10 |
complex_branching | 8 |
deeply_nested | 5 |
stale_complex | 2 |
hub_function | 1 |
These labels 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, hub_function, long_function, stale_complex.
Reproduce This Analysis
git clone https://github.com/moby/moby
cd moby
git checkout 275015b0e7d1e17941df3dad1503263d653588e5
hotspots analyze . --mode snapshot --explain-patterns --force
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 →