Back to home

Go Internals - Essentials

6 min read
Cover Image for Go Internals - Essentials
Lucas LemosLucas Lemos

Introduction

After the Data Structures with Golang series, the next step is to look under the hood. Not at how to use slices, maps, or goroutines, but at how the language and runtime actually implement them.

This article is Essentials: a map of the territory. Each following article does a deep dive into one layer of Go internals — Scheduler, Memory, Allocator, Garbage Collector, Built-in Types, Interfaces, Reflection, Concurrency, and Compiler. We will read real source, run small experiments, and connect behavior you already know from application code to the machinery behind it.

The entire series targets Go 1.26. Internals evolve between releases; when something shifts in a future version, we will call it out in the relevant article.

What "Go internals" actually covers

When people say "Go internals," they usually mean two cooperating systems:

  1. The toolchain (cmd/compile, cmd/link, cmd/asm) — turns your .go files into a binary.
  2. The runtime (runtime) — linked into every Go program and responsible for scheduling, memory, GC, stacks, and the implementation of core language features.

Your main function is not the real entry point. The linker sets up the runtime first; only then does your code run.

Most articles focus on the runtime side of that diagram: what happens after the binary starts, and what the compiler had to arrange so the runtime can do its job. Compiler closes the series on the toolchain side — how your source becomes the code and metadata the runtime consumes.

Series roadmap

The runtime is not one big ball of code. It is organized in layers that depend on each other. Later articles assume earlier ones; that is why we split the series vertically instead of dumping everything into a single post.

ArticleLayerWhat we will go deep on
Essentials (this article)OverviewBuild pipeline, runtime role, how to read the source
SchedulerSchedulingGoroutines vs OS threads, G/M/P, work stealing, preemption
MemoryMemory modelStack growth, heap, escape analysis, when values leave the stack
AllocatorHeap allocationSize classes, spans, mcache / mcentral / mheap
Garbage CollectorReclamationTri-color marking, write barriers, phases, STW, GOGC
Built-in TypesCore typesslice header, string layout, map (Swiss table / maps.Map) internals
InterfacesDispatchiface / eface, itabs, dynamic dispatch cost
ReflectionType metadatareflect.Type / reflect.Value, wrapping runtime descriptors, Call and allocs
ConcurrencySync primitivesChannel buffers, select, mutex and semaphore primitives
CompilerToolchainSSA, inlining, bounds-check elimination, what the compiler emits for the runtime

Built-in Types deliberately bridges back to the data structures series: you already implemented hash tables; we will see what the runtime's map does differently.

From go build to a running program

A minimal program is enough to anchor the rest of the series:

package main

import "fmt"

func main() {
  fmt.Println("hello")
}
go build -o hello .
./hello

Under the hood, go build invokes the compiler and linker. The compiler emits machine code and metadata the runtime needs: stack maps, type descriptors, relocation info. The linker produces an executable with the runtime package linked in.

At process start, the runtime initializes itself (scheduler, memory arenas, GC, signal handlers) before main.main runs. If you have never stepped through that path, it is worth opening src/runtime/proc.go and searching for runtime.main once; Scheduler will refer back to it.

How to follow along

You do not need to fork the Go repo, but you do need the standard library and runtime source for Go 1.26 — the same version as your toolchain.

go version   # should report go1.26.x
go env GOROOT

Runtime code lives at $GOROOT/src/runtime/. Compiler code is under $GOROOT/src/cmd/compile/. Browsing on go.dev/src is the same tree; select the go1.26 tag to match this series.

Useful habits for the deep dives:

  • Small reproducers — ten lines of Go that exhibit one behavior (escape, GC pressure, goroutine churn).
  • Compiler feedbackgo build -gcflags="-m" for escape analysis (Memory).
  • Runtime tracingGODEBUG=gctrace=1,... for GC (Garbage Collector), scheduler traces where relevant (Scheduler).
  • Debugger — Delve to inspect stack frames and goroutine state while reading scheduler concepts.

We will not treat unsafe as a goal. It appears only where it clarifies how the runtime represents values in memory.

What you should already know

This series is deeper than the data structures articles, but it is not a Go tutorial. Comfortable reading of:

  • pointers, structs, and interfaces at the language level
  • goroutines and channels in everyday code
  • basic complexity intuition (O(1) vs O(n))

is enough. If you finished the data structures series, you are in the right place.

How this differs from the data structures series

Data structures seriesGo internals series
GoalImplement and compare structuresExplain runtime and language machinery
Code focusYour own Container implementationsSmall programs + reading runtime source
DepthAlgorithm + API trade-offsMemory layout, syscalls, scheduler invariants
SplitOne structure category per articleOne internals layer per article

Same author voice, same preference for concrete examples — but the "artifact" is understanding, not a reusable library.

Conclusion

Essentials sets the route: toolchain builds the binary, runtime owns execution and memory, and each upcoming article isolates one layer for a proper deep dive.

Next up is Scheduler — how goroutines multiplex onto OS threads, what G, M, and P mean, and why your concurrent Go code behaves the way it does under load.