Go Channel Deadlock Visualizer
Write a few goroutines and channels in the grammar below and this explores every interleaving of them. A deadlock comes with the shortest path to it and what each goroutine is stuck on; no deadlock means none is reachable, because the whole state space was walked. Anything it cannot decide it calls undecided.
Nothing you paste is transmitted. There is no endpoint behind this page: the Go that explores the state space is compiled to WebAssembly and runs in this tab, so what you write never leaves the browser.
The input is a small language, not Go
This is the whole reason the page can be honest about its answers. Deciding whether real Go can deadlock is not possible in general — loops, conditionals, recursion and channels made at run time put the state space beyond any finite search — so a tool that accepted Go source could only ever approximate, and an approximation that says “no deadlock” is worse than no tool at all.
So the language here has no loops, no conditionals other than select, no values and no function calls. What is left is exactly the part of a program that can deadlock. The state space is finite by construction, every interleaving is explored, and the answer is a proof rather than a sample.
Anything outside the grammar is refused with a line number. It is never quietly reinterpreted into something nearby.
Start from one of these
The grammar
Published in full, because the parser refuses everything outside it. This block is the same string the tool itself checks against, so the two cannot drift apart.
The semantics it models, and nothing more
- A send on an unbuffered channel blocks until a receiver is ready, and the two proceed together as one step.
- A send on a buffered channel blocks only while the buffer is full.
- A receive blocks while the buffer is empty and the channel is open.
- A receive from a closed channel never blocks: it drains the buffer and then returns immediately, for ever.
- A send on a closed channel panics. Closing a closed channel panics.
- The program ends when main returns, whatever the other goroutines are doing. It deadlocks when main has not returned and nothing at all can move.
It does not model scheduling order, GOMAXPROCS, timing, or which of several ready goroutines the runtime would pick. None of those is a documented guarantee, so an answer resting on one would be a guess. Every interleaving is explored instead, and the result says which outcomes are reachable.
What each verdict means
No deadlock is a proof, not a search that came up empty: the whole state space was walked and none of it deadlocks. Deadlock on every execution means there is no way through. Deadlock on some executions is the worst kind to have in real code — it depends on scheduling, so it passes in testing and fails in production.
Undecided means the walk hit its state budget and stopped. It has not seen every execution, so it cannot say there is no deadlock, and it does not pretend otherwise. That substitution — reporting an incomplete search as safety — is the one thing this page must never do.
Why the examples can be trusted
Every example in the library above also exists as a real Go program in the repository, and the test suite compiles and runs each one on every build. The verdict shown here has to match what the Go runtime actually did — a deadlock, a panic, or a clean exit. An example the runtime contradicts fails the build.
That check is what stands between this page and a confident wrong answer. A model of channel semantics is easy to write and easy to get subtly wrong, and every such mistake renders as something entirely plausible.
A cycle is one shape of deadlock, not the definition
When the goroutines are waiting on each other in a ring, the ring is named. But a single goroutine waiting on a channel nothing will ever touch is a deadlock with no cycle at all, and so are two goroutines stuck on unrelated channels. Where there is no cycle the page says so rather than staying quiet, because “no cycle” and “no deadlock” are very different findings.
The other tools
- Go Time Layout Converter — strftime to Go, and back — Convert a C strftime format string to a Go reference layout and back, directive by directive, with every unmappable directive named rather than guessed.
- Go Method Sets — why *T implements the interface and T does not — Paste a type and an interface and get both method sets computed by go/types, with the compiler's own error and the rule it follows from.
- Go Struct Field Alignment — offsets, padding, and what reordering saves — Paste a struct and get every field's offset, the padding between them, the total size, and a reordered version with the saving — for the architecture you choose.
- Go Slice Growth — what append actually does to the capacity — Watch a slice's capacity grow append by append: the formula's answer, the allocator's rounding, the size class it lands in, and the bytes left behind.
- Go Goroutine Dump Analyzer — read a SIGQUIT or panic stack dump — Paste a goroutine dump and get it grouped by state and by stack, with wait durations and the stacks held by far more goroutines than they should be.
- GOGC and GOMEMLIMIT Calculator — which one is actually deciding your heap — Compute both documented target-heap rules, see which one binds, and find out when GOGC has stopped having any effect at all.
- Go Benchmark Comparison — is that 4% real? — Paste two go test -bench outputs and get the Mann-Whitney U comparison: median change, p-value, and an honest answer when there is not enough data to tell.
- Go Minimal Version Selection — why that version, and who asked for it — Paste a go mod graph and see minimal version selection worked out: every version required, who required it, and the path from your main module to the requirement that won.
- Go Escape Analysis Explainer — read what -gcflags=-m actually said — Paste the output of go build -gcflags=-m and get it grouped by cause, with the -m -m reason chains read as derivations and every form the grammar does not cover named rather than guessed.