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 pageshowhide
explore
- Goroutine Scheduler (GMP)35 questions
- Parking and Waking Goroutines4 questions
- The G, M and P Model5 questions
- Run Queues and Work Stealing5 questions
- Runtime Timer Heaps4 questions
- Preemption and Safe Points4 questions
- Blocking Syscalls and the Netpoller4 questions
- schedtrace and Stack Dumps4 questions
- Threads, sysmon and LockOSThread5 questions
- Memory Allocation and Stacks16 questions
- Escape Analysis4 questions
- Growable Goroutine Stacks4 questions
- The Heap Allocator4 questions
- sync.Pool and Reuse4 questions
- Garbage Collector26 questions
- Tri-Color Concurrent Marking4 questions
- Write Barriers and Stop-the-World Phases4 questions
- Heap Target Pacing4 questions
- gctrace and MemStats4 questions
- Finalizers, Cleanups and Weak References6 questions
- Mark Assists and Stalls4 questions
- Value Representation21 questions
- The Slice Header4 questions
- append, Growth and Retention4 questions
- Swiss Tables and Growth4 questions
- String and []byte Representation5 questions
- Struct Alignment and Padding4 questions
- Interface Representation13 questions
- eface, iface and the itab4 questions
- Type Assertion and Boxing Cost4 questions
- Type Metadata in Binaries5 questions
- Deferred Calls and Panics12 questions
- How defer Is Implemented4 questions
- Panic and Stack Unwinding4 questions
- Reading a Traceback4 questions
- Program Startup and Exit13 questions
- Bootstrap Before main5 questions
- Fatal Throws Versus Panics4 questions
- os.Exit and runtime.Goexit4 questions
questions
136 · 7 sectionsIn Go's goroutine scheduler, what do G, M and P stand for, and what does each represent?
basics
~20 sG 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.
When a goroutine blocks on a channel receive, what happens to the OS thread it was running on?
basics
~20 sNothing 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.
In Go, does one goroutine blocked in a file-read syscall stop the other goroutines from running?
basics
~20 sNo. 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.
When a goroutine calls time.Sleep, what does the Go runtime block, and what wakes it?
basics
~20 sNothing 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.
What exactly does GOMAXPROCS limit in a Go program, and what does it not limit?
basics
~20 sGOMAXPROCS 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.
In Go, is it safe to return a pointer to a local variable from a function?
basics
~20 sYes. 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.
Why does `make([]byte, 0, 4096)` cut allocations, and how does it differ from `make([]byte, 4096)`?
basics
~20 smake([]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.
How do Go's mcache, mcentral and mheap cooperate on a small heap allocation?
basics
~20 sEach 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.
Why does Go's heap allocator use 24 bytes to store a 17-byte object?
basics
~20 sGo'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.
How does a goroutine's stack grow when its initial ~2 KB is not enough?
basics
~20 sEach 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.
What does runtime.SetFinalizer do in Go, and what does the runtime guarantee about when it runs?
basics
~20 sruntime.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.
With GOGC=100, what heap size does Go's garbage collector set as its next goal?
basics
~20 sGOGC 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.
Does Go's garbage collector move objects in memory or use generations?
basics
~20 sGo'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.
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?
basics
~20 sTrack 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.
In Go, what is a garbage-collection mark assist, and which goroutine pays for it?
basics
~20 sA 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.
Why does ranging over the same Go map twice give the entries in a different order?
basics
~20 sGo'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.
Why must you write s = append(s, x) in Go instead of just calling append(s, x)?
basics
~20 sappend 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.
What three fields does a Go slice header hold at runtime, and what does each mean?
basics
~20 sA 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.
What does a Go string value hold at runtime, and what does assigning one to another variable copy?
basics
~20 sA 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.
Why can a Go function mutate a slice's elements but not change the caller's length?
basics
~20 sA 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.
Does Go's comma-ok type assertion v, ok := x.(T) cost more at runtime than the single-value form?
basics
~20 sNeither 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.
A wiring function returns a nil *fileSink as a Sink interface, the caller's nil guard passes, and the first call panics — why?
basics
~20 sThe 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.
When an *os.File is assigned to an io.Writer variable, what does that interface variable hold?
basics
~20 sA 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.
Why is a compiled Go hello-world binary a couple of megabytes rather than a few kilobytes?
basics
~20 sGo 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.
Why is asserting a Go any value to a concrete type cheaper than asserting it to another interface?
basics
~20 sA 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.
Why does `defer f.Close()` inside a Go for loop keep every file open until the function returns?
basics
~20 sGo'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.
What does a Go panic do to the goroutine's stack, and how does the program end if nothing recovers it?
basics
~20 sA 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.
In a Go panic traceback, what do a frame's function line and its indented file:line line each tell you?
basics
~20 sEach 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.
In Go, what are open-coded defers and when does the compiler fall back to runtime defer records?
basics
~20 sAn 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.
In Go, what does `return 5` compile to in a function with deferred calls and a named result?
basics
~20 sIt 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.
What runs between a Go binary starting and the first statement of main.main?
basics
~20 sThe 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.
In Go, what happens to deferred functions and buffered output when a program calls os.Exit?
basics
~10 sos.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.
What does Go's `fatal error: all goroutines are asleep - deadlock!` actually mean?
basics
~10 sGo'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.
When the Go runtime throws `fatal error: concurrent map writes`, why does no deferred function run?
basics
~20 sBecause 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.
On which goroutine do Go's package init functions run, and what happens if one blocks?
basics
~20 sAll 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.