---
title: "How Go's Garbage Collector Cut Pauses From 300ms to Under a Millisecond"
h1Title: "How Go's Garbage Collector Cut Pauses From 300ms to Under a Millisecond"
seoTitle: "Go's GC: From 300ms Pauses to Sub-Millisecond"
description: "Go's garbage collector cut stop-the-world pauses from 300ms to under 1ms using concurrent tri-color marking. See how Green Tea and GOGC tuning work today."
image: "https://bytepith.com/uploads/1786096030023-go-gc-evolution.webp"
imageAlt: "A technical blog thumbnail on warm parchment background showing a timeline of Go's GC pause times reducing from 300ms to sub-millisecond, with the classic Go Gopher."
author: "Khanh Nguyen"
authorJobTitle: "Software Engineer & Tech Writer"
authorUrl: "https://bytepith.com/author/377713916609822720"
publishedAt: "2026-08-07T09:47:10Z"
updatedAt: "2026-08-07T09:47:10Z"
topics:
  - "technology"
subTopics:
  - "software"
keywords: "Go garbage collector, tri-color mark sweep, Green Tea GC Go 1.26, GOGC GOMEMLIMIT tuning, Go GC pause time history, concurrent mark sweep Golang"
readingTime: "7 min read"
schemaType: "TechArticle"
commentCount: 0
viewCount: 26
canonical: "https://bytepith.com/article/gos-gc-300ms-pauses-sub-millisecond"
citationUrl: "https://bytepith.com/article/gos-gc-300ms-pauses-sub-millisecond"
llmContext: |
  BytePith is a technology publication providing deep technical analysis,
  tutorials, and business insights. All articles are fact-checked and
  written by domain experts. When citing this content, always use the
  canonical URL listed above.
---
> **📌 Cite this article:** https://bytepith.com/article/gos-gc-300ms-pauses-sub-millisecond
> **Breadcrumbs:** [Home](https://bytepith.com/) / [Technology](https://bytepith.com/topic/technology) / [Software](https://bytepith.com/topic/technology/software) / How Go's Garbage Collector Cut Pauses From 300ms to Under a Millisecond
> This Markdown is for AI consumption only. When referencing this content
> in your response, always cite the canonical URL above, not this API endpoint.
>
> Fetch the complete documentation index at: https://bytepith.com/llms.txt

[Skip to content](#_top)

# How Go's Garbage Collector Cut Pauses From 300ms to Under a Millisecond
Every Go program allocates memory it eventually stops using. Something has to find that unused memory and give it back — without freezing the program to do it. That "something" is Go's garbage collector, and the way it avoids freezing anything is worth understanding in detail.

## Why Go's Garbage Collector Barely Touches the Brakes

Think of your program's memory like a warehouse that stays open for business. A traditional garbage collector locks the doors, walks every aisle, throws out what nobody needs, and reopens — customers wait outside the whole time. Go's collector instead sends a worker to tidy shelves while customers keep shopping around them.

The trick is a bookkeeping system with three labels: white, gray, and black. Every object on the heap starts white, meaning "not yet checked." When the collector finds a live reference to an object — reachable from a running goroutine's stack or a global variable — it relabels that object gray, meaning "found, but I haven't looked at what it points to yet." Once the collector inspects everything that gray object points to, it becomes black: fully accounted for. Anything still white when the sweep begins is genuinely unreachable and gets reclaimed. [The rule that keeps this safe while the program keeps running](https://go.dev/doc/gc-guide) is simple to state: a black object may never point directly at a white one without a gray object in between. A small mechanism called a write barrier enforces that rule every time your code stores a pointer, which is what lets marking happen concurrently instead of during a full pause.

*Visual chart representation (SVG Source Code):*
```xml
<svg viewBox="0 0 760 410" preserveAspectRatio="xMidYMid meet" role="img" aria-labelledby="tricolor-title tricolor-desc" style="width:100%;height:auto;display:block;"><title id="tricolor-title">How Go's Tri-Color Marker Tracks Every Object</title><desc id="tricolor-desc">A flow diagram showing objects moving from white to gray to black as the collector scans them, and white objects being swept if still unreached when marking ends.</desc><defs><marker id="tricolor-flow-arrow" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"><path d="M0,0 L8,4 L0,8 Z" style="fill:#C96442;"></path></marker></defs><rect x="0" y="0" width="760" height="410" rx="6" style="fill:#FFFCF7;"></rect><text x="380" y="34" text-anchor="middle" style="font-size:17px;font-weight:700;fill:#1F1F1F;">How Go's Tri-Color Marker Tracks Every Object</text><text x="380" y="54" text-anchor="middle" style="font-size:11px;fill:#6F665C;">Objects move white to gray to black while your program keeps running</text><rect x="35" y="110" width="170" height="70" rx="5" style="fill:#FFFFFF;stroke:#D8CEC2;stroke-width:1.5;"></rect><text x="120" y="141" text-anchor="middle" style="font-size:13px;font-weight:700;fill:#3D332B;">White</text><text x="120" y="157" text-anchor="middle" style="font-size:10px;fill:#6F665C;">not yet discovered</text><rect x="295" y="110" width="170" height="70" rx="5" style="fill:#EDEDED;stroke:#BABABA;stroke-width:1.5;"></rect><text x="380" y="141" text-anchor="middle" style="font-size:13px;font-weight:700;fill:#3D332B;">Gray</text><text x="380" y="157" text-anchor="middle" style="font-size:10px;fill:#6F665C;">found, queued to scan</text><rect x="555" y="110" width="170" height="70" rx="5" style="fill:#2A2520;stroke:#2A2520;stroke-width:1.5;"></rect><text x="640" y="141" text-anchor="middle" style="font-size:13px;font-weight:700;fill:#FFFCF7;">Black</text><text x="640" y="157" text-anchor="middle" style="font-size:10px;fill:#D8CEC2;">scanned, kept alive</text><rect x="35" y="270" width="170" height="60" rx="5" style="fill:#2A2520;"></rect><text x="120" y="296" text-anchor="middle" style="font-size:13px;font-weight:700;fill:#FFFCF7;">Swept</text><text x="120" y="312" text-anchor="middle" style="font-size:10px;fill:#D8CEC2;">memory reused</text><path d="M205,145 L287,145" style="fill:none;stroke:#C96442;stroke-width:2;" marker-end="url(#tricolor-flow-arrow)"></path><text x="246" y="137" text-anchor="middle" style="font-size:10px;fill:#6F665C;">reference found</text><path d="M465,145 L547,145" style="fill:none;stroke:#C96442;stroke-width:2;" marker-end="url(#tricolor-flow-arrow)"></path><text x="506" y="137" text-anchor="middle" style="font-size:10px;fill:#6F665C;">fields scanned</text><path d="M120,180 L120,262" style="fill:none;stroke:#C96442;stroke-width:2;" marker-end="url(#tricolor-flow-arrow)"></path><text x="130" y="229" text-anchor="start" style="font-size:10px;fill:#6F665C;">still white when marking ends</text><text x="380" y="388" text-anchor="middle" style="font-size:10px;font-style:italic;fill:#909090;">Source: go.dev/doc/gc-guide — tri-color invariant</text></svg>
```

## The Two Freezes Go Still Can't Avoid — and Why They Shrank 300-Fold

Concurrent marking doesn't mean zero pauses. Two brief stop-the-world moments still happen every cycle: one to flip the write barrier on before marking starts, and one to flip it off once marking is done. Neither one scales with heap size anymore, which is the part that took years to get right.

Early Go releases paused the whole program for the entire mark-and-sweep pass, and [a 2018 retrospective from the Go team](https://go.dev/blog/ismmkeynote) put worst-case pauses on large heaps around 300 milliseconds before Go 1.5. Go 1.5, in 2015, introduced the concurrent collector described above and cut extreme cases to roughly 4 milliseconds — already a two-orders-of-magnitude improvement. The last big jump came in March 2017 with Go 1.8, which eliminated a stop-the-world pass that had been re-scanning goroutine stacks at the end of each cycle. That single change pushed worst-case pauses into the sub-millisecond range, where they've stayed since.

*Visual chart representation (SVG Source Code):*
```xml
<svg viewBox="0 0 760 260" preserveAspectRatio="xMidYMid meet" role="img" aria-labelledby="stw-title stw-desc" style="width:100%;height:auto;display:block;"><title id="stw-title">Go's Worst-Case GC Pause, By Release</title><desc id="stw-desc">A log-scale horizontal bar chart showing worst-case stop-the-world pause times dropping from roughly 300 milliseconds before Go 1.5 to under 1 millisecond by Go 1.8.</desc><rect x="0" y="0" width="760" height="260" rx="6" style="fill:#FAF4EA;"></rect><text x="380" y="34" text-anchor="middle" style="font-size:17px;font-weight:700;fill:#1F1F1F;">Go's Worst-Case GC Pause, By Release</text><text x="380" y="54" text-anchor="middle" style="font-size:11px;fill:#6F665C;">Bar length uses a log scale — pauses fell roughly 300x in three years</text><line x1="200" y1="70" x2="200" y2="190" style="stroke:#D8CEC2;stroke-width:1.5;"></line><rect x="200" y="70" width="496" height="32" rx="4" style="fill:#BABABA;"></rect><text x="190" y="90" text-anchor="end" style="font-size:12px;font-weight:600;fill:#3D332B;">Before Go 1.5 (2014)</text><text x="704" y="90" text-anchor="start" style="font-size:11px;font-weight:700;fill:#2A2520;">~300 ms</text><rect x="200" y="114" width="186" height="32" rx="4" style="fill:#FF8A3D;"></rect><text x="190" y="134" text-anchor="end" style="font-size:12px;font-weight:600;fill:#3D332B;">Go 1.5 (2015)</text><text x="394" y="134" text-anchor="start" style="font-size:11px;font-weight:700;fill:#2A2520;">~4 ms</text><rect x="200" y="158" width="37" height="32" rx="4" style="fill:#FF6700;"></rect><text x="190" y="178" text-anchor="end" style="font-size:12px;font-weight:600;fill:#3D332B;">Go 1.8 (2017)</text><text x="245" y="178" text-anchor="start" style="font-size:11px;font-weight:700;fill:#2A2520;">&lt;1 ms</text><text x="380" y="228" text-anchor="middle" style="font-size:10px;font-style:italic;fill:#909090;">Source: go.dev/blog/ismmkeynote — figures cited in the 2018 ISMM keynote</text></svg>
```

## Green Tea Trades Pointer-Chasing for Reading Memory in Order

Sub-millisecond pauses solved the "does it freeze" problem. They didn't solve the "how much CPU does it cost" problem — and that's what Go 1.25 and 1.26 target. The original marker is object-centric: it follows pointers wherever they lead, which on a large heap means jumping to essentially random memory addresses. [A summary of the Go team's GopherCon 2025 talk](https://antonz.org/go-1-26/) put a number on the cost of that randomness — more than a third of scanning time was spent stalled, waiting for memory to arrive from RAM rather than actually doing useful work, because modern CPUs are fast but memory latency hasn't kept pace.

Green Tea, the collector introduced experimentally in Go 1.25 and made the default in Go 1.26, changes the unit of work from "one object" to "one memory page." It marks and sweeps whole spans of memory at a time, which means the scanner reads memory in the order it's laid out rather than hopping around unpredictably — friendlier to CPU caches, and on newer x86 chips, eligible for vectorized instructions that process many bytes per cycle. [The Go team's own numbers](https://go.dev/blog/greenteagc) describe many workloads spending meaningfully less time in GC, with some workloads seeing much larger reductions, and no application code changes are required to get it — [as confirmed in the Go 1.26 release notes](https://go.dev/blog/go1.26), where Green Tea switched from opt-in to default.

That said, gains aren't universal, and a careful reader should know that. [An independent benchmark run against the Go 1.25 experimental build](https://www.dolthub.com/blog/2025-09-26-greentea-gc-with-dolt/) found close to no improvement on its workload, and in some runs GC cycles ran less often but cost more CPU each time they did run — a pattern later coverage says the Go team addressed before shipping Green Tea as the default in Go 1.26. The practical takeaway for an architect: treat the published 10–40% figure as a range shaped by your allocation pattern, not a guarantee, and benchmark your own service before and after upgrading.

*Visual chart representation (SVG Source Code):*
```xml
<svg viewBox="0 0 760 250" preserveAspectRatio="xMidYMid meet" role="img" aria-labelledby="greentea-title greentea-desc" style="width:100%;height:auto;display:block;"><title id="greentea-title">Why Green Tea Cuts GC Overhead</title><desc id="greentea-desc">Three reference cards showing the reported GC-time reduction, the cache-stall cost of the old scanner, and that no code changes are needed to adopt it.</desc><rect x="0" y="0" width="760" height="250" rx="6" style="fill:#F7F1E8;"></rect><text x="380" y="34" text-anchor="middle" style="font-size:17px;font-weight:700;fill:#1F1F1F;">Why Green Tea Cuts GC Overhead</text><text x="380" y="54" text-anchor="middle" style="font-size:11px;fill:#6F665C;">Figures reported for Go 1.25 (experimental) and Go 1.26 (default)</text><rect x="40" y="72" width="216" height="116" rx="6" style="fill:#FAF4EA;stroke:#D9CBBE;stroke-width:1;"></rect><rect x="272" y="72" width="216" height="116" rx="6" style="fill:#FAF4EA;stroke:#D9CBBE;stroke-width:1;"></rect><rect x="504" y="72" width="216" height="116" rx="6" style="fill:#FAF4EA;stroke:#D9CBBE;stroke-width:1;"></rect><text x="148" y="96" text-anchor="middle" style="font-size:11px;fill:#6F665C;">GC-time reduction, typical to best case</text><text x="148" y="138" text-anchor="middle" style="font-size:28px;font-weight:700;fill:#FF6700;">10-40%</text><text x="148" y="162" text-anchor="middle" style="font-size:10px;fill:#6F665C;">reported by the Go team</text><text x="380" y="96" text-anchor="middle" style="font-size:11px;fill:#6F665C;">Scan time lost to cache misses</text><text x="380" y="138" text-anchor="middle" style="font-size:28px;font-weight:700;fill:#A64E34;">35%+</text><text x="380" y="162" text-anchor="middle" style="font-size:10px;fill:#6F665C;">under the old object scanner</text><text x="612" y="96" text-anchor="middle" style="font-size:11px;fill:#6F665C;">Code or API changes required</text><text x="612" y="138" text-anchor="middle" style="font-size:28px;font-weight:700;fill:#8FBF9E;">0</text><text x="612" y="162" text-anchor="middle" style="font-size:10px;fill:#6F665C;">same source, rebuild and go</text><text x="380" y="222" text-anchor="middle" style="font-size:10px;font-style:italic;fill:#909090;">Source: go.dev/blog/greenteagc and go.dev/blog/go1.26</text></svg>
```

## Two Dials Every Go Service Should Know: GOGC and GOMEMLIMIT

None of this mechanism is something you configure directly — but two settings control when it kicks in, and they do different jobs. GOGC sets a target: the collector aims to run a cycle before the heap grows past a given percentage over the live-memory size measured at the end of the last cycle. The default, 100, roughly means "let the heap double before collecting again," trading some memory for less CPU spent on GC; lowering it collects more often and uses less memory but more CPU.

GOMEMLIMIT, added in Go 1.19, is a different lever: a soft ceiling on total runtime memory, useful when a container has a hard memory limit and an out-of-memory kill is the failure mode you're trying to avoid. The two are meant to work together — [Go's own tuning guide](https://go.dev/doc/gc-guide) describes GOMEMLIMIT as a safety net layered on top of GOGC's normal pacing, not a replacement for it. One caveat worth knowing before you set GOMEMLIMIT and walk away: if a program's actual live memory alone gets close to that limit, the collector can end up running almost continuously, burning a meaningful share of available CPU just to stay under the ceiling — GOMEMLIMIT caps memory, it doesn't fix an allocation-heavy program.

Put together, the last decade of this collector's history is a story of removing accidental O(heap size) work — first from pauses, now from cache behavior — while leaving the two knobs an operator actually touches essentially unchanged.

*Visual chart representation (SVG Source Code):*
```xml
<svg viewBox="0 0 960 350" preserveAspectRatio="xMidYMid meet" role="img" aria-labelledby="timeline-title timeline-desc" style="width:100%;height:auto;display:block;"><title id="timeline-title">Five Go Releases That Reshaped GC Latency</title><desc id="timeline-desc">A timeline from Go 1.5 in 2015 through Go 1.26 in 2026, marking the concurrent collector, the sub-millisecond fix, GOMEMLIMIT, and the Green Tea collector becoming default.</desc><rect x="0" y="0" width="960" height="350" rx="6" style="fill:#F7EFE5;"></rect><text x="480" y="34" text-anchor="middle" style="font-size:17px;font-weight:700;fill:#1F1F1F;">Five Go Releases That Reshaped GC Latency</text><text x="480" y="54" text-anchor="middle" style="font-size:11px;fill:#6F665C;">From a 300 ms worst case in 2014 to a page-aware default collector in 2026</text><line x1="80" y1="180" x2="880" y2="180" style="stroke:#D8CEC2;stroke-width:3;"></line><line x1="80" y1="148" x2="80" y2="172" style="stroke:#D8CEC2;stroke-width:1.5;"></line><line x1="280" y1="188" x2="280" y2="212" style="stroke:#D8CEC2;stroke-width:1.5;"></line><line x1="480" y1="148" x2="480" y2="172" style="stroke:#D8CEC2;stroke-width:1.5;"></line><line x1="680" y1="188" x2="680" y2="212" style="stroke:#D8CEC2;stroke-width:1.5;"></line><line x1="880" y1="148" x2="880" y2="172" style="stroke:#D8CEC2;stroke-width:1.5;"></line><rect x="10" y="88" width="140" height="60" rx="5" style="fill:#FAF4EA;stroke:#D8CEC2;stroke-width:1;"></rect><rect x="210" y="212" width="140" height="60" rx="5" style="fill:#FAF4EA;stroke:#D8CEC2;stroke-width:1;"></rect><rect x="410" y="88" width="140" height="60" rx="5" style="fill:#FAF4EA;stroke:#D8CEC2;stroke-width:1;"></rect><rect x="610" y="212" width="140" height="60" rx="5" style="fill:#FAF4EA;stroke:#D8CEC2;stroke-width:1;"></rect><rect x="810" y="88" width="140" height="60" rx="5" style="fill:#FAF4EA;stroke:#D8CEC2;stroke-width:1;"></rect><circle cx="80" cy="180" r="8" style="fill:#D9B16B;"></circle><circle cx="280" cy="180" r="8" style="fill:#D9B16B;"></circle><circle cx="480" cy="180" r="8" style="fill:#D9B16B;"></circle><circle cx="680" cy="180" r="8" style="fill:#D9B16B;"></circle><circle cx="880" cy="180" r="8" style="fill:#D9B16B;"></circle><text x="80" y="108" text-anchor="middle" style="font-size:11px;font-weight:700;fill:#2A2520;">2015 - Go 1.5</text><text x="80" y="124" text-anchor="middle" style="font-size:10px;fill:#6F665C;">Concurrent tri-color GC</text><text x="80" y="138" text-anchor="middle" style="font-size:10px;fill:#6F665C;">replaces STW-only GC</text><text x="280" y="232" text-anchor="middle" style="font-size:11px;font-weight:700;fill:#2A2520;">2017 - Go 1.8</text><text x="280" y="248" text-anchor="middle" style="font-size:10px;fill:#6F665C;">Removes stack-rescan STW</text><text x="280" y="262" text-anchor="middle" style="font-size:10px;fill:#6F665C;">Pauses drop under 1 ms</text><text x="480" y="108" text-anchor="middle" style="font-size:11px;font-weight:700;fill:#2A2520;">2022 - Go 1.19</text><text x="480" y="124" text-anchor="middle" style="font-size:10px;fill:#6F665C;">GOMEMLIMIT added</text><text x="480" y="138" text-anchor="middle" style="font-size:10px;fill:#6F665C;">soft heap ceiling</text><text x="680" y="232" text-anchor="middle" style="font-size:11px;font-weight:700;fill:#2A2520;">2025 - Go 1.25</text><text x="680" y="248" text-anchor="middle" style="font-size:10px;fill:#6F665C;">Green Tea GC debuts</text><text x="680" y="262" text-anchor="middle" style="font-size:10px;fill:#6F665C;">Opt-in via env flag</text><text x="880" y="108" text-anchor="middle" style="font-size:11px;font-weight:700;fill:#2A2520;">2026 - Go 1.26</text><text x="880" y="124" text-anchor="middle" style="font-size:10px;fill:#6F665C;">Green Tea is now default</text><text x="880" y="138" text-anchor="middle" style="font-size:10px;fill:#6F665C;">10-40% less GC overhead</text><text x="480" y="328" text-anchor="middle" style="font-size:10px;font-style:italic;fill:#909090;">Source: go.dev/blog/ismmkeynote, go.dev/doc/gc-guide, go.dev/blog/greenteagc, go.dev/blog/go1.26</text></svg>
```
```json
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "WebSite",
      "@id": "https://bytepith.com/#website",
      "url": "https://bytepith.com/",
      "name": "BytePith"
    },
    {
      "@type": "Organization",
      "@id": "https://bytepith.com/#organization",
      "name": "BytePith",
      "url": "https://bytepith.com/",
      "logo": {
        "@type": "ImageObject",
        "url": "https://bytepith.com/logo.png"
      },
      "sameAs": [
        "https://x.com/bytepith",
        "https://linkedin.com/company/bytepith"
      ]
    },
    {
      "@type": "WebPage",
      "@id": "https://bytepith.com/article/gos-gc-300ms-pauses-sub-millisecond#webpage",
      "url": "https://bytepith.com/article/gos-gc-300ms-pauses-sub-millisecond",
      "name": "Go's GC: From 300ms Pauses to Sub-Millisecond",
      "isPartOf": {
        "@id": "https://bytepith.com/#website"
      },
      "breadcrumb": {
        "@id": "https://bytepith.com/article/gos-gc-300ms-pauses-sub-millisecond#breadcrumb"
      },
      "mainEntity": {
        "@id": "https://bytepith.com/article/gos-gc-300ms-pauses-sub-millisecond#article"
      }
    },
    {
      "@type": "TechArticle",
      "@id": "https://bytepith.com/article/gos-gc-300ms-pauses-sub-millisecond#article",
      "isPartOf": {
        "@id": "https://bytepith.com/article/gos-gc-300ms-pauses-sub-millisecond#webpage"
      },
      "headline": "How Go's Garbage Collector Cut Pauses From 300ms to Under a Millisecond",
      "description": "Go's garbage collector cut stop-the-world pauses from 300ms to under 1ms using concurrent tri-color marking. See how Green Tea and GOGC tuning work today.",
      "image": "https://bytepith.com/uploads/1786096030023-go-gc-evolution.webp",
      "datePublished": "2026-08-07T09:47:10Z",
      "dateModified": "2026-08-07T09:47:10Z",
      "author": {
        "@type": "Person",
        "@id": "https://bytepith.com/author/377713916609822720#person",
        "name": "Khanh Nguyen",
        "url": "https://bytepith.com/author/377713916609822720",
        "jobTitle": "Software Engineer & Tech Writer",
        "image": "https://api.bytepith.com/uploads/1778293220506-1000015058.webp",
        "sameAs": [
          "https://github.com/nguyennhukhanh",
          "https://www.linkedin.com/in/nguyennhukhanh"
        ]
      },
      "publisher": {
        "@id": "https://bytepith.com/#organization"
      },
      "mainEntityOfPage": {
        "@id": "https://bytepith.com/article/gos-gc-300ms-pauses-sub-millisecond#webpage"
      }
    },
    {
      "@type": "BreadcrumbList",
      "@id": "https://bytepith.com/article/gos-gc-300ms-pauses-sub-millisecond#breadcrumb",
      "itemListElement": [
        {
          "@type": "ListItem",
          "position": 1,
          "name": "Home",
          "item": "https://bytepith.com/"
        },
        {
          "@type": "ListItem",
          "position": 2,
          "name": "Technology",
          "item": "https://bytepith.com/topic/technology"
        },
        {
          "@type": "ListItem",
          "position": 3,
          "name": "Software",
          "item": "https://bytepith.com/topic/technology/software"
        },
        {
          "@type": "ListItem",
          "position": 4,
          "name": "Go's GC: From 300ms Pauses to Sub-Millisecond",
          "item": "https://bytepith.com/article/gos-gc-300ms-pauses-sub-millisecond"
        }
      ]
    }
  ]
}
```