What exactly does GOMAXPROCS limit in a Go program, and what does it not limit?
answer
- count one letter, not the other two
- millions of one, a handful of the other
- blocked goroutines cost nothing here
- simultaneously, not concurrently
- it is not a thread ceiling
basics
~20 sGOMAXPROCS sets the number of Ps, capping how many goroutines execute Go code at the same time. It does not cap how many goroutines you can create, and it is not a ceiling on the OS threads the runtime may create.
solid answer
~40 s`GOMAXPROCS` is the number of Ps, and since an OS thread must hold a P to execute Go code, it is the ceiling on **simultaneous execution of Go code** — nothing else. It is not a goroutine limit: a program with `GOMAXPROCS=4` can hold a million goroutines, because a goroutine that is blocked on a channel, sleeping or waiting on I/O holds no P at all and costs nothing against this budget. It is also not a thread ceiling; the number of OS threads is managed by the runtime separately. Read it with `runtime.GOMAXPROCS(0)`, set it with the `GOMAXPROCS` environment variable or `runtime.GOMAXPROCS(n)`, and remember the default is derived from the environment at startup rather than being a fixed number.
go deeper
Remember the one-line meaning: it is how many goroutines can run Go code at the same time, not how many goroutines you may start. Know that runtime.GOMAXPROCS(0) reads it.
Explain it through the P count and be explicit about the two things it does not bound — goroutine count and thread count — and about blocked goroutines holding no P.
Demonstrate that you check the value rather than assume it: log it against runtime.NumCPU() at startup, and know that in a CPU-limited container on recent Go the two numbers routinely disagree.
Frame it as the per-process CPU-parallelism budget your platform hands to every service, and be clear that raising it buys parallel execution only when there is real CPU behind it.
## The precise meaning `GOMAXPROCS` sets the number of **Ps** in the Go runtime. A P is a scheduling context, and an OS thread (an M) must be holding one in order to execute Go code. Compose those two facts and you get the whole definition: > `GOMAXPROCS` is the maximum number of goroutines that can be executing Go code simultaneously. Everything else people believe about it is folklore. ## What it does not limit **Not the number of goroutines.** Goroutines are cheap runtime objects; you can create far more than you have Ps, and that is the normal way to write Go. With `GOMAXPROCS=4`, a server can hold 200,000 goroutines, four of which are executing Go code at any instant and the rest of which are runnable-and-waiting or blocked. **Not a count of "active" work.** A goroutine blocked on a channel operation, sleeping on a timer, or waiting for I/O is not holding a P. The P moves on to another ready goroutine immediately. This is why the classic "one goroutine per connection" design works with a tiny `GOMAXPROCS`: connections are mostly idle, and idle costs nothing against this budget. **Not the OS thread count.** The runtime decides how many Ms to create, for its own reasons, and that number is governed separately from `GOMAXPROCS`. A process can perfectly well show more threads than it has Ps. **Not a guarantee of CPU.** It bounds how many goroutines *may* run Go code at once; whether the machine actually gives you that many CPUs is a scheduling question for the operating system, not something the Go runtime can promise. ## Reading and setting it - `runtime.GOMAXPROCS(0)` returns the current value without changing it. This is the honest way to find out what your process actually got. - `runtime.GOMAXPROCS(n)` with `n >= 1` sets it and returns the previous value. Resizing the P set is a global runtime operation, so do it at startup, not per request. - The `GOMAXPROCS` environment variable sets it before `main` runs. - `runtime.NumCPU()` is a *different* number: the count of logical CPUs the process may use. It is frequently equal to `GOMAXPROCS` and frequently not, and assuming they are the same is a common source of surprise. ## Where the default comes from Historically the default was simply the number of logical CPUs usable by the process — which on Linux already accounts for the process's CPU affinity mask, so a process restricted to two CPUs got two. Since **Go 1.25**, on Linux, the default also takes the **cgroup CPU bandwidth limit** into account: if the container's CPU limit is lower than the usable CPU count, the lower value wins. The runtime also re-reads these inputs periodically and may update `GOMAXPROCS` while the program runs. Setting the value explicitly — environment variable or `runtime.GOMAXPROCS` — opts out of that automatic updating, and `runtime.SetDefaultGOMAXPROCS()` puts the program back on the runtime-derived default. The practical consequence: on a large host running small containers, an older build sees the host's CPU count and a Go 1.25+ build sees roughly the container's limit. Same code, different P count. ## Why the misconception is expensive Believing `GOMAXPROCS` caps goroutines leads people to raise it hoping to "allow more concurrency", which does nothing for the number of goroutines and only widens the parallel-execution budget. Believing it caps threads leads to capacity models that are simply wrong. And believing it equals `runtime.NumCPU()` leads to sizing per-shard structures from the wrong number in a container. ## The one-line answer If you have one sentence: *it is the number of Ps, so it is the ceiling on goroutines executing Go code at the same instant — not a limit on goroutines, not a limit on threads.*
- How do you read the current value from inside a running program?`runtime.GOMAXPROCS(0)` returns the current setting without changing it. Pair it with `runtime.NumCPU()`, which reports the logical CPUs the process may use: printing both at startup tells you immediately whether the runtime derived a P count that matches the machine you think you are on.
- Does a goroutine blocked on a channel receive consume part of the GOMAXPROCS budget?No. A blocked goroutine holds no P; the runtime parks it and the P immediately picks up another ready goroutine. Only goroutines actually executing Go code occupy the budget, which is why a server can hold hundreds of thousands of mostly-idle goroutines with a P count in the single digits.
- If GOMAXPROCS is 8 on a 64-core machine, can a CPU-bound Go program use more than 8 cores?Not for Go code. At most eight goroutines execute Go code at any instant, so a purely CPU-bound Go workload occupies about eight cores' worth of the machine and the rest sits idle as far as your program is concerned. Raising the P count is the only way to widen that, not creating more goroutines.
saying these in an interview costs you the question
- Calls GOMAXPROCS the maximum number of goroutines
- Says it hard-caps the process's OS threads
- Claims blocked goroutines occupy a P
- Raises it expecting more concurrency, not more parallelism
- Assumes it always equals runtime.NumCPU()