skip to content

Runtime Machinery

What the runtime does underneath your code: the scheduler that multiplexes goroutines onto threads, the concurrent collector, how built-in types are laid out, and the machinery behind defer and panic.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

explore

questions

136 · 7 sections

In Go's goroutine scheduler, what do G, M and P stand for, and what does each represent?

level: juniorimportance: must knowfreq 58%
basics
~20 s

G is a goroutine, M is an OS thread, and P is a scheduling context an M must hold to run Go code. GOMAXPROCS sets how many Ps exist, so it caps how much Go code runs at once.

open as a page

When a goroutine blocks on a channel receive, what happens to the OS thread it was running on?

level: juniorimportance: must knowfreq 62%
basics
~20 s

Nothing blocks at the operating-system level. Go's runtime parks the goroutine, marking it waiting and detaching it from the thread, then runs another runnable goroutine on that same thread. The thread keeps doing useful work.

open as a page

In Go, does one goroutine blocked in a file-read syscall stop the other goroutines from running?

level: juniorimportance: must knowfreq 62%
basics
~20 s

No. When a goroutine enters a system call that blocks, the Go runtime detaches its P (the scheduling context) from that OS thread, and another thread picks the P up and keeps running the remaining goroutines.

open as a page

When a goroutine calls time.Sleep, what does the Go runtime block, and what wakes it?

level: juniorimportance: must knowfreq 55%
basics
~20 s

Nothing at the OS level blocks. The Go runtime parks that goroutine and records its wake time in a runtime timer heap, freeing the thread for other goroutines; when the deadline passes the goroutine becomes runnable again.

open as a page

What exactly does GOMAXPROCS limit in a Go program, and what does it not limit?

level: middleimportance: must knowfreq 68%
basics
~20 s

GOMAXPROCS sets the number of Ps, capping how many goroutines execute Go code at the same time. It does not cap how many goroutines you can create, and it is not a ceiling on the OS threads the runtime may create.

open as a page

In Go, is it safe to return a pointer to a local variable from a function?

level: juniorimportance: must knowfreq 72%
basics
~20 s

Yes. Go has no dangling pointers: at compile time escape analysis sees that the address outlives the function's frame, so the compiler allocates that variable on the heap instead of the stack. The garbage collector frees it later.

open as a page

Why does `make([]byte, 0, 4096)` cut allocations, and how does it differ from `make([]byte, 4096)`?

level: juniorimportance: must knowfreq 60%
basics
~20 s

make([]byte, 0, 4096) reserves a 4096-byte array but starts with length 0, so appends fill it without reallocating. make([]byte, 4096) sets the length to 4096, so the slice already holds 4096 zero bytes and append adds data after them.

open as a page

How do Go's mcache, mcentral and mheap cooperate on a small heap allocation?

level: middleimportance: must knowfreq 50%
basics
~20 s

Each P owns an mcache holding one span per size class, so the usual allocation is a lock-free pop. An exhausted span is refilled from that class's shared mcentral, which takes fresh spans from the global mheap.

open as a page

Why does Go's heap allocator use 24 bytes to store a 17-byte object?

level: juniorimportance: should knowfreq 32%
basics
~20 s

Go's heap allocator rounds every small allocation up to one of about 68 fixed size classes, and 24 is the first class that fits 17 bytes. Fixed sizes let one span hold identical objects with no per-object header.

open as a page

How does a goroutine's stack grow when its initial ~2 KB is not enough?

level: juniorimportance: should knowfreq 58%
basics
~20 s

Each Go function starts by checking whether enough stack space is left. If not, it calls into the runtime, which allocates a new stack of double the size, copies the old stack into it, and lets the function continue.

open as a page

What does runtime.SetFinalizer do in Go, and what does the runtime guarantee about when it runs?

level: juniorimportance: must knowfreq 38%
basics
~20 s

runtime.SetFinalizer registers a function the Go runtime may call some time after an object becomes unreachable. The runtime guarantees almost nothing: it runs on a later garbage-collection cycle, or not at all, and never at program exit.

open as a page

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

level: juniorimportance: must knowfreq 55%
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.

open as a page

Does Go's garbage collector move objects in memory or use generations?

level: juniorimportance: must knowfreq 55%
basics
~20 s

Go's garbage collector is a concurrent tri-colour mark-and-sweep collector that is neither generational nor moving. A heap object keeps the same address for its whole life, and there are no young or old generations to promote between.

open as a page

A Go daemon's RSS stays at 900 MB while gctrace reports a 60 MB live heap after a scrape spike — how do you tell a leak from retained memory?

level: seniorimportance: must knowfreq 55%
basics
~20 s

Track the live-heap figure in gctrace across many cycles, not one sample. Flat while resident memory stays high means the runtime is holding freed spans it has not given back. A real leak makes that figure climb steadily.

open as a page

In Go, what is a garbage-collection mark assist, and which goroutine pays for it?

level: juniorimportance: should knowfreq 36%
basics
~20 s

A mark assist is garbage-collection marking work the Go runtime makes an allocating goroutine do itself. During a collection cycle, allocating memory charges your own goroutine some scanning, so the busiest allocators pay for the collector's backlog.

open as a page

Why does ranging over the same Go map twice give the entries in a different order?

level: juniorimportance: must knowfreq 68%
basics
~20 s

Go's runtime starts every map iteration at a randomly chosen position, so map order is undefined and changes from loop to loop. For stable output, copy the keys into a slice, sort it, and index the map in that order.

open as a page

Why must you write s = append(s, x) in Go instead of just calling append(s, x)?

level: juniorimportance: must knowfreq 85%
basics
~20 s

append returns a new slice value and never updates the slice you passed in. If the backing array has no spare room, append allocates a bigger array, so only the returned value is guaranteed to see the appended element.

open as a page

What three fields does a Go slice header hold at runtime, and what does each mean?

level: juniorimportance: must knowfreq 78%
basics
~20 s

A Go slice value is three words: a pointer to the first element it covers in a backing array, a length, and a capacity. The elements live in that array, not inside the slice value.

open as a page

What does a Go string value hold at runtime, and what does assigning one to another variable copy?

level: juniorimportance: must knowfreq 62%
basics
~20 s

A Go string value is a two-word header: a pointer to an array of bytes plus a length. Assigning or passing a string copies only that header (16 bytes on a 64-bit platform), never the text it points at.

open as a page

Why can a Go function mutate a slice's elements but not change the caller's length?

level: middleimportance: must knowfreq 72%
basics
~20 s

A slice is passed by value, so the callee gets its own copy of the three-word header. Writes through the shared pointer reach the caller's elements; assigning a new length or capacity only changes the callee's copy.

open as a page

Does Go's comma-ok type assertion v, ok := x.(T) cost more at runtime than the single-value form?

level: juniorimportance: must knowfreq 60%
basics
~20 s

Neither form is faster. Both run the same dynamic-type check and differ only in failure: the comma-ok form yields T's zero value and false, while the single-value form panics with a message naming the actual and the requested type.

open as a page

A wiring function returns a nil *fileSink as a Sink interface, the caller's nil guard passes, and the first call panics — why?

level: seniorimportance: must knowfreq 58%
basics
~20 s

The interface's type word is set to the Sink and *fileSink pairing while its data word is nil, so the value is not equal to nil. The guard passes, the call dispatches, and the method dereferences a nil receiver.

open as a page

When an *os.File is assigned to an io.Writer variable, what does that interface variable hold?

level: juniorimportance: should knowfreq 45%
basics
~20 s

A pair of words: the dynamic type *os.File, plus a data word pointing at the file. The variable is not a copy of the file, and it carries the concrete type along, which is why %T prints *os.File.

open as a page

Why is a compiled Go hello-world binary a couple of megabytes rather than a few kilobytes?

level: juniorimportance: should knowfreq 42%
basics
~20 s

Go links statically and ships the whole runtime — collector, goroutine scheduler, allocator — plus per-type metadata, the function and line tables behind stack traces, and debug information. That fixed baseline dwarfs a tiny program's own code.

open as a page

Why is asserting a Go any value to a concrete type cheaper than asserting it to another interface?

level: middleimportance: should knowfreq 35%
basics
~20 s

A concrete target is one comparison: the value's dynamic type against that type's descriptor, whose address is fixed at build time. An interface target makes the runtime match the whole method set, then memoise the answer.

open as a page

Why does `defer f.Close()` inside a Go for loop keep every file open until the function returns?

level: juniorimportance: must knowfreq 62%
basics
~20 s

Go's defer is scoped to the function call, not to the block or the loop iteration. Every trip through the loop registers one more deferred Close against the same frame, and none of them run until the enclosing function returns.

open as a page

What does a Go panic do to the goroutine's stack, and how does the program end if nothing recovers it?

level: juniorimportance: must knowfreq 80%
basics
~20 s

A panic stops normal execution and unwinds the goroutine's stack, running every deferred call on the way up. Unrecovered, the runtime prints the panic value plus a stack trace to standard error and exits the whole process with status 2.

open as a page

In a Go panic traceback, what do a frame's function line and its indented file:line line each tell you?

level: juniorimportance: must knowfreq 70%
basics
~20 s

Each Go traceback frame is two lines: the qualified function name with hex words for its arguments, then an indented source file and line plus a +0x offset into the function. Frames print innermost first, so the top one panicked.

open as a page

In Go, what are open-coded defers and when does the compiler fall back to runtime defer records?

level: middleimportance: should knowfreq 40%
basics
~20 s

An open-coded defer is one the Go compiler inlines: it keeps the call in stack slots, sets a bit in a bitmask, and calls it at each return. Defers in a loop, or more than eight per function, fall back to runtime records.

open as a page

In Go, what does `return 5` compile to in a function with deferred calls and a named result?

level: middleimportance: should knowfreq 55%
basics
~20 s

It compiles to three steps: assign 5 into the named result's slot, run the frame's deferred calls, then return to the caller. Because the defers run in between, a deferred closure writing to that named result changes what the caller receives.

open as a page

What runs between a Go binary starting and the first statement of main.main?

level: juniorimportance: must knowfreq 68%
basics
~20 s

The Go runtime starts first: it sets up the heap, the goroutine scheduler and the garbage collector, then creates the main goroutine. That goroutine initialises every imported package - package-level variables, then init functions - and only then calls main.main.

open as a page

In Go, what happens to deferred functions and buffered output when a program calls os.Exit?

level: juniorimportance: must knowfreq 65%
basics
~10 s

os.Exit ends the process immediately. Deferred functions never run, so anything a defer would have flushed or closed is lost: bytes still sitting in a bufio.Writer never reach the file or the terminal.

open as a page

What does Go's `fatal error: all goroutines are asleep - deadlock!` actually mean?

level: juniorimportance: must knowfreq 60%
basics
~10 s

Go's scheduler found nothing left to run: every goroutine is parked on a channel operation, a lock or a similar wait, so none can ever wake another. The runtime aborts the whole program immediately.

open as a page

When the Go runtime throws `fatal error: concurrent map writes`, why does no deferred function run?

level: middleimportance: must knowfreq 52%
basics
~20 s

Because it is not a panic. A fatal runtime error skips stack unwinding entirely: the runtime freezes the other goroutines, prints the banner and every goroutine's stack, and exits. Deferred calls and recover never enter the picture.

open as a page

On which goroutine do Go's package init functions run, and what happens if one blocks?

level: middleimportance: should knowfreq 44%
basics
~20 s

All package initialisation runs sequentially on the main goroutine, before main.main is called. If one init function blocks - on a channel, a lock or a network call - startup stops there: the process stays alive but main never runs.

open as a page