skip to content

With GOGC=100, what heap size does Go's garbage collector set as its next goal?

level: juniorimportance: must knowfreq 55%

answer

  1. a ratio, not a byte count
  2. measured from the live heap
  3. one hundred means grow by one hundred percent
  4. recomputed after every cycle
  5. default is a doubling

basics

~20 s

GOGC is a growth percentage, not a size. At the default GOGC=100 the runtime sets the next heap goal at roughly double the live heap the last cycle measured: 200 MB live gives a goal near 400 MB.

solid answer

~50 s

GOGC states how much the heap may grow over the live heap, so the goal for the next cycle is roughly `live x (1 + GOGC/100)`. At the default 100 that is a doubling: a cycle that ends with 200 MB live sets the next goal near 400 MB. Current Go also folds the root set - goroutine stacks and globals - into the growth term, so the real goal sits a little above a plain doubling. The goal is recomputed after every cycle from the live heap that cycle actually found, so it tracks the program: a service whose live set grows collects at higher and higher heap sizes. Two guards sit around it - the runtime will not collect below a small minimum heap, about 4 MB at GOGC=100, and it forces a cycle if two minutes pass without one.

code

text · 6 lines
text
GOGC=50   goal ~= 200 MB + 50% of 200 MB  = ~300 MB
GOGC=100  goal ~= 200 MB + 100% of 200 MB = ~400 MB   (default)
GOGC=200  goal ~= 200 MB + 200% of 200 MB = ~600 MB

# the real goal is slightly higher: current Go scales the
# root set (goroutine stacks and globals) into the growth term too

go deeper

for a junior

Be ready to say GOGC is a percentage of growth over the live heap and that the default, 100, puts the next goal at about double. The key move is refusing to read it as a byte limit.

for a middle

Explain that the goal is recomputed after every cycle from the live heap that cycle measured, that current Go scales the root set into the growth term too, and why a minimum heap exists at all.

for a senior

Show that collection frequency tracks allocation rate divided by the headroom between live heap and goal, so a service whose live set grows collects less often per byte allocated while each cycle scans more.

for a principal

Own the framing that GOGC fixes an exchange rate between memory and CPU, and that the same rate behaves very differently for a service with a 50 MB live set and one with a 5 GB one.

## What the goal is Go's garbage collector does not run on a timer and does not run at a fixed heap size. It runs when the heap has grown by a set proportion over the amount of memory that was still reachable at the end of the previous collection. That reachable amount is the **live heap**; the proportion is `GOGC`. The rule in its simplest form: ``` next heap goal = live heap x (1 + GOGC/100) ``` `GOGC` is an environment variable read at process start, and its default is `100`. One hundred percent growth on top of the live heap means the goal is a doubling. Some worked numbers at the default: | live heap after a cycle | goal for the next cycle | | --- | --- | | 20 MB | ~40 MB | | 200 MB | ~400 MB | | 2 GB | ~4 GB | At `GOGC=50` the goal is `live x 1.5`; at `GOGC=200` it is `live x 3`. ## It is a ratio, and that is the whole design The single most common misreading is to hear `GOGC=100` as "collect at 100 MB". It is a percentage with no unit attached to it. The consequence of using a ratio rather than a fixed byte count is that the collector scales itself: the same setting gives a small process small, cheap cycles and a large process large, infrequent ones, without anyone having to know the program's working set in advance. The amount of room between the live heap and the goal is the **headroom**: `goal - live`. Headroom is what the program gets to allocate into before the collector has to run again. Because a program allocates at some rate, the frequency of collections is roughly `allocation rate / headroom`. At a fixed `GOGC`, headroom grows in proportion to the live heap, so a process whose live set doubles collects half as often per byte allocated - while each of those cycles has twice as much to scan. That trade is exactly what the ratio encodes: memory bought with CPU, at a fixed exchange rate. ## The refinement current Go actually uses The plain formula above describes the semantics, but the runtime's pacer computes the goal from the total amount of work a cycle has to do, which is the live heap **plus the roots** - goroutine stacks and global variables, both of which must be scanned every cycle. The growth term is applied to that combined figure, so the true goal is a little higher than `live x 2` at the default, and a program with an unusual amount of stack or global data feels the difference. For interview purposes the doubling is the right mental model; knowing that stacks and globals are folded in is the extra half-step. ## The goal is recomputed, not fixed Nothing about the goal is decided at startup. Every cycle ends by measuring how much memory survived, and that measurement sets the next goal. A server that warms a cache during its first minute will see its goal climb as the cache fills, then settle. A process that frees a large structure will see the goal fall on the very next cycle. Two guards sit around the formula: - **A minimum heap.** Doubling a 1 KB heap would mean collecting constantly during startup, so the runtime will not trigger below a small floor - roughly 4 MB at the default `GOGC`, scaled with the setting. Small programs therefore appear not to collect at all. - **A forced cycle.** If two minutes pass with no collection, the runtime forces one. A mostly idle process that allocates almost nothing still collects occasionally, which is what lets memory be returned to the operating system rather than sitting reachable-but-unswept forever. A memory limit, if one is configured, can also pull the goal below what `GOGC` alone would compute; when both are in play the runtime uses the smaller of the two goals. ## Goal versus trigger One more distinction matters for reading anything the runtime reports. The goal is where the heap is meant to be when a cycle **finishes**, not where the cycle **starts**. Because marking runs concurrently with the program, the collector has to begin somewhere below the goal and aim to land on it. So a heap that sits at 350 MB with a 400 MB goal may already be in the middle of a collection. ## What GOGC costs you in each direction Raising `GOGC` widens the headroom: fewer cycles, less CPU spent collecting, a higher peak heap. Lowering it narrows the headroom: more cycles, more CPU, a lower peak heap. Neither direction changes how much memory is *live* - it only changes how much garbage is allowed to accumulate before it is reclaimed.

  • A cycle ends with 50 MB live and GOGC is 200. Roughly where is the next goal?
    Around 150 MB. GOGC=200 asks for 200% growth on top of the live heap, so the goal is about live x 3. The true figure is a little higher because current Go scales the root set - goroutine stacks and globals - into the growth term as well as the live heap.
  • Why does a tiny program not collect constantly, given that doubling a 1 KB heap is still tiny?
    The runtime enforces a minimum heap below which it will not trigger a cycle at all - roughly 4 MB at the default GOGC, scaled with the setting. Without it, startup would be a storm of collections over a heap too small for any of them to be worth the CPU.
  • Does the heap goal stay fixed for the life of the process?
    No. It is recomputed at the end of every cycle from the live heap that cycle measured, so it rises as a service warms its caches and falls when a large structure is released. A configured memory limit can also pull the goal lower than GOGC alone would set it.

GOGC is like a rule that says "clean the room when the mess is as big as everything you are keeping", not "clean the room at 9pm" or "clean it when the floor hits ten square metres".

saying these in an interview costs you the question

  • Calls GOGC a heap size limit in megabytes
  • Says GOGC=100 means collect when the heap reaches 100 MB
  • Thinks the goal is fixed at process start
  • Believes GOGC counts allocations or seconds between cycles
  • Assumes GOGC changes how much memory is live