skip to content

How does runtime/debug.SetGCPercent differ from setting GOGC, and what does it return?

level: middleimportance: nice to knowfreq 28%

answer

  1. the environment is read once, at start-up
  2. the function hands you back the old value
  3. restore it with a defer
  4. negative turns it off, zero does not
  5. process-wide, so not a library's call to make

basics

~20 s

GOGC is read from the environment once at start-up; debug.SetGCPercent changes the same target percentage in a running process, takes effect immediately, and returns the previous setting so it can be restored. A negative value disables collection.

solid answer

~40 s

`GOGC` is consulted when the runtime initialises, so changing the environment afterwards — including calling `os.Setenv` — does nothing to a live process. `runtime/debug.SetGCPercent(percent int) int` sets the same growth percentage at run time, applies it right away, and returns the value it replaced, which is what lets you widen the allowance for one phase and put it back with a `defer`. Passing a negative value disables collection, the equivalent of `GOGC=off`; passing 0 does *not* disable it — it asks the collector to run essentially continuously, which is a classic footgun. Because the setting is process-wide, a library that calls it in `init` silently overrides whatever the operator deployed, so it belongs in `main`, in a test, or behind an explicit operations endpoint.

code

go · 4 lines
go
prev := debug.SetGCPercent(400)
defer debug.SetGCPercent(prev)

generateHourlyReports()

go deeper

for a junior

Know that the collector's growth percentage can be set two ways: the GOGC environment variable at start-up, or debug.SetGCPercent from code while the program runs.

for a middle

Be able to give the signature and the return value, explain the defer-and-restore idiom, and state that a negative argument disables collection while zero makes it run constantly.

for a senior

An interviewer expects the operational judgment: keep the value in the environment so it can be changed without a release, and use the runtime call only for a scoped phase or a controlled experiment you can revert.

for a principal

Own the rule that process-wide runtime settings are not a library's to touch, and make sure the service's chosen value stays visible to whoever holds the memory budget rather than buried in code.

## Same knob, different door `GOGC` and `runtime/debug.SetGCPercent` write to the same runtime setting — the percentage of the live set that may be allocated before the next collection cycle. They differ in **when** and **who**. ### GOGC: start-up only, operator-owned The runtime reads `GOGC` from the environment during initialisation, before `main` runs. Consequences worth stating explicitly: - Changing the environment of a running process does nothing. `os.Setenv("GOGC", "400")` at run time is inert as far as the collector is concerned — a real and frequently-seen mistake. - It is owned by whoever deploys the binary, which is a feature: the person holding the memory budget can change it without a code release. ### SetGCPercent: run time, program-owned ```go func SetGCPercent(percent int) int ``` It sets the new target percentage, applies it immediately, and **returns the previous setting**. Three details follow: 1. **Immediate effect.** Lowering the percentage can bring the next collection forward straight away, because the target is recomputed against the current live set. Raising it relaxes the target at once. 2. **The return value is the restore handle.** The idiom is to capture it and put the old value back: ```go prev := debug.SetGCPercent(400) defer debug.SetGCPercent(prev) ``` This is what makes a temporary, phase-scoped change safe: the code does not need to know, or hard-code, what the operator configured. 3. **Negative disables; zero does not.** A negative percentage turns automatic collection off — the programmatic `GOGC=off`. Zero means "allow zero percent growth", i.e. collect as soon as the heap exceeds the live set, which in practice means near-continuous collection. People reach for `0` meaning "off" and get the most expensive possible setting instead. ### With collection disabled, runtime.GC() still works Disabling the automatic trigger does not remove manual control. `runtime.GC()` forces a full collection and blocks until it completes, so a batch job can turn the automatic trigger off for a phase and collect explicitly at a natural boundary — between reports, between files — where a pause is harmless. ### Why a library must not call it The setting is **process-wide**. A library that calls `debug.SetGCPercent` in its `init` function reaches out of its own package and reconfigures the whole binary, overriding the deployment's `GOGC` invisibly. The symptom is a service whose memory profile changes for no reason anyone can find in its own configuration, and two dependencies that both do it will simply fight, last writer winning. Treat it as an application-level, `main`-level, or operations-level decision: - **Legitimate:** a batch phase in `main` that widens the allowance and restores it; an operations endpoint that flips the value during a load test so you can compare settings without a redeploy; a test that pins the setting for determinism. - **Illegitimate:** any package that is imported by others. ### Choosing between the two in practice Default to the environment variable. It keeps the value visible in the deployment, changeable by whoever owns the capacity budget, and different per environment without a build. Reach for `SetGCPercent` when the value genuinely must vary *within* one process's lifetime — a program with a distinct load phase and a distinct serving phase — or when you want to run a controlled experiment on a live instance and put the old value back afterwards.

  • What does debug.SetGCPercent(0) do, and why do people get it wrong?
    It sets a zero percent growth allowance, meaning the runtime aims to collect as soon as the heap exceeds the live set — effectively continuous collection and the most CPU-hungry setting available. People type 0 intending "off", but disabling requires a negative percentage. The two are one keystroke apart and produce opposite behaviour.
  • Can a program still force a collection after disabling the automatic trigger?
    Yes. runtime.GC() performs a full collection and blocks until it finishes, independently of the target percentage. A batch program can therefore turn automatic collection off for a phase and collect explicitly at a boundary where a pause costs nothing, which is the only shape in which disabling is a sane idea.
  • A dependency calls debug.SetGCPercent in its init function. Why is that a bug report?
    The setting is process-wide, so an imported package silently overrides the value the deployment chose, and two such dependencies fight with last-writer-wins. The service's memory behaviour then has no explanation anywhere in its own configuration. Tuning the collector is an application or operations decision; a library's job is to allocate less, not to reconfigure its host.

saying these in an interview costs you the question

  • Believes os.Setenv on GOGC affects the running process
  • Uses debug.SetGCPercent(0) meaning to disable collection
  • Discards the return value and then hard-codes a restore value
  • Calls SetGCPercent from a library or an imported package's init
  • Thinks a runtime change only applies from the next cycle onward