skip to content

In go test, how many tests run at once when they call t.Parallel, and does that span packages?

level: middleimportance: nice to knowfreq 28%

answer

  1. two knobs, two different scopes
  2. one inside a binary, one across binaries
  3. both default to GOMAXPROCS
  4. cross-package concurrency is between processes

basics

~20 s

Inside one package's test binary, the number of tests running together is capped by go test's -parallel flag, which defaults to GOMAXPROCS. Separately, go test over several packages runs their binaries at the same time, capped by -p.

solid answer

~40 s

There are two different concurrency limits and they operate at different scopes. Within a single package's test binary, tests that call `t.Parallel()` run simultaneously up to `-parallel n`, which defaults to `GOMAXPROCS`; a parent test waiting on its parallel children releases its own slot, so nesting does not deadlock even at 1. Across packages, `go test ./...` builds and runs each package's binary as its own process, and `-p n` caps how many of those run at once — also defaulting to `GOMAXPROCS`, and it applies whether or not any test calls `t.Parallel()`. The distinction matters for isolation: cross-package concurrency is between separate processes, so environment variables and working directories are independent there, while `-parallel` concurrency is inside one process and shares all of it.

go deeper

for a junior

Know that tests do not all run at once by default, and that the number of overlapping parallel tests is a flag on go test rather than something written in the test file.

for a middle

Distinguish the two limits by scope and name their defaults: one caps parallel tests inside a single package binary, the other caps how many package binaries run at once. Both default to GOMAXPROCS.

for a senior

Use the limits diagnostically and operationally: serialise to prove tests share state, lower cross-package concurrency when binaries contend on one external resource, and recognise that cross-package concurrency is process-level, so it isolates memory but not ports or files.

for a principal

Decide the CI posture: what concurrency the build machines can sustain, whether the suite's shared external resources are the real ceiling, and whether tuning flags is buying time that should instead be spent removing coupling between tests.

## Two knobs, two scopes A `go test ./...` run has concurrency at two levels, and confusing them leads to wrong conclusions about what is safe. **Inside one test binary — `-parallel`.** Each package compiles to its own test binary. Within that binary, tests run one at a time by default; the only ones that overlap are those that called `t.Parallel()`. How many of those may be running simultaneously is set by `go test -parallel n`. The default is `GOMAXPROCS`. Setting `-parallel 1` effectively serialises the parallel tests again, which is a useful bisecting tool when a suite starts failing intermittently after parallelism is introduced. **Across packages — `-p`.** The `go` command itself runs multiple package builds and multiple test binaries concurrently, capped by `-p n`, again defaulting to `GOMAXPROCS`. This has nothing to do with `t.Parallel()`: a package whose every test is strictly sequential still runs at the same time as the test binaries of other packages. ## Why the distinction decides isolation questions The two forms of concurrency have completely different sharing properties: - `-p` concurrency is **between processes**. Two packages running at the same time have separate environments, separate working directories, separate package-level variables, separate memory. One package's `t.Setenv` cannot be seen by another package's test. What they *do* share is the machine: ports, files on disk, a database, CPU and memory. - `-parallel` concurrency is **inside one process**. Everything global is shared: the environment, the working directory, package-level state, singletons, open files. This is the level at which data races between tests are possible, and it is why `t.Setenv` and `t.Chdir` panic in a parallel test. So "my tests already run concurrently across packages, therefore my package is fine" is a non sequitur about memory safety, but it is a real warning about external resources: two packages that both bind a fixed port, or both write to the same file outside their own scratch space, will collide under `-p` even with no `t.Parallel()` anywhere. ## Choosing values Most of the time both defaults are right. Reasons to change them: - Lower `-p` when the test binaries are memory-hungry or contend on one external resource, so the machine is not oversubscribed. - Lower `-parallel` to 1 while investigating a suite that only fails when tests overlap; if the failure disappears, the tests share state. - Raise `-parallel` above `GOMAXPROCS` when the parallel tests are dominated by waiting rather than CPU, so more can be in flight than there are cores to run them. Both are flags of the `go` command rather than properties of the test source, so a team can tune them per CI job without editing tests. ## The slot-release detail One implementation detail is worth knowing because it explains a non-obvious absence of deadlock: when a parent test finishes its body and waits for its parallel children, it gives its own slot back to the limit. Otherwise a parent holding a slot while its child waits for one would hang at `-parallel 1`. The same release makes deep nesting of parallel subtests workable. ## What neither knob does Neither flag makes anything safe. They cap concurrency; they do not create isolation. A test that mutates shared state is a bug at `-parallel 4` and a latent bug at `-parallel 1` — the second just hides it until the day the limit or the machine changes.

  • If no test in a package calls t.Parallel, can that package's tests still run at the same time as another package's?
    Yes. The `go` command runs several package test binaries concurrently, capped by `-p`, regardless of `t.Parallel()`. Those binaries are separate processes, so memory and environment are isolated, but they still contend for shared machine resources such as ports, files and a database.
  • What does setting -parallel 1 tell you when an intermittent failure disappears?
    That the tests are not independent. Serialising the parallel tests removes the only difference — overlap inside one process — so a failure that vanishes points at shared mutable state, a shared external resource, or an ordering assumption between tests, rather than at the code under test.
  • Would raising -parallel above GOMAXPROCS ever make sense?
    Yes, when the parallel tests spend their time waiting rather than computing — reading files, sleeping, talking to a stub server. More can be in flight than there are cores, and the run gets shorter. For CPU-bound tests it just adds contention with no benefit.

saying these in an interview costs you the question

  • Thinks -p and -parallel are the same flag
  • Believes t.Parallel is what makes packages run concurrently
  • Assumes cross-package concurrency shares the environment
  • Says lowering the limit makes shared state safe
  • Guesses the default is 1 rather than GOMAXPROCS