Go interview questions: short answers
The short answer from every lesson in the track — the lesson’s own text, minus the “Short answer:” label — and the questions from its “Next they ask” section. The answers to those questions, the mechanism and the measurements behind it all are in the linked lesson.
01
Structs and methods
A struct is a composite value type of named fields that lie in memory one after another. Assignment and passing copy it whole, but the copy is shallow: slices, maps and pointers inside stay shared, and
slices.Clonewithmaps.Clonedo not change that — they copy one level. There are no constructors — a struct's zero value consists of the zero values of its fields, and a good type works with it directly.==is allowed if all fields are comparable; an interface field gets past the compiler, and the panic comes at run time. Methods are declared on a defined type of your own package and are essentially functions with the receiver as the first argument.Next they ask
02
Pointers
A pointer is a value that holds the address of a variable. In Go everything is passed by value, the pointer too: a function receives a copy of the address. A write through the pointer (
*p = 42) is visible to the caller because both copies lead to one variable; assigning to the parameter itself (p = nil) is not, because only the copy changes.Next they ask
03
Reflection
Reflection in Go is the
reflectpackage, which, given a value in an interface, yields its type (reflect.Type) and the value itself (reflect.Value). Reading is always allowed — an unexported field throughInt(),String()and the like, but not throughInterface().SetandSetIntwork only ifCanSetof the specificValueistrue: the value is addressable and was not obtained through an unexported field; that is why, to replace the variable itself, you pass a pointer and callElem(). A slice element and a map entry are reachable even without a pointer —IndexandSetMapIndexhave their own conditions.encoding/json,fmtand everything that works with types unknown at compile time use it; the cost is above all the lookup by name, and libraries cache it per type.Next they ask
04
io.Reader and io.Writer
io.Readeris an interface with one methodRead(p []byte) (n int, err error): it puts up tolen(p)bytes intopand returns how many it put, plus an error.io.Writeris the same for writing, but having written less thanlen(p)it must return an error, and the error is checked even after a full write. When reading, you handle thenbytes first and then look aterr, andio.EOFis the normal end of a stream. For ready-made tasks —io.Copy,io.ReadFull,bufio.Next they ask
05
Go tooling
go buildcompiles and leaves a binary,go runbuilds into a temporary directory without debug information and runs it; both take packages from the build cache, whose key is the content of the inputs.gofmtputs code into the one canonical format, and nobody argues with it.go vetcatches errors the compiler lets through — a wrongPrintfverb, a copied mutex, unreachable code;go testruns only part of its checks, sogo vet ./...in CI is a separate step.Next they ask
06
Functions and closures
A function in Go is a value: it has a type and the zero value
nil, it is passed and returned; it can be compared only withnil. A closure is a function literal that refers to variables of the surrounding function, and those variables are shared: the closure sees their current value at the moment of the call, not a copy from the moment of creation.Next they ask
07
Packages and layout
A package is a directory of files with one name in the
packageline. From outside only names with an uppercase first letter are visible. Initialization goes like this: first the imported packages — each once; inside a package — variables by dependencies, then allinit()functions in file order; and only thenmain.Next they ask
08
Modules and dependencies
A module is a set of packages released under one version; the
go.modat its root lists requirements — a module and a version "no lower than" — andgo.sumstores the hashes of the archive and ofgo.modfor each module needed. The version is chosen by minimal version selection: for each module, the highest of those required in the graph, not the latest in the proxy.Next they ask
09
JSON and tags
Exported fields go into JSON, the key is the field name or the
json:"…"tag.omitemptyskipsfalse,0,"",niland empty slices, maps and arrays of length zero, but not structs — which is whytime.Time{}comes out as"0001-01-01T00:00:00Z".Unmarshalmatches fields without regard to case, skips extra keys silently and does not touch fields absent from the input.Next they ask
10
Concurrency patterns
A worker pool is N goroutines that read jobs from one channel and write results to another; the job channel is closed by the feeder, the result channel by a separate goroutine after
wg.Wait(). For a CPU-bound task no more thanGOMAXPROCSworkers run at once, so there is nowhere for a gain beyond that to come from; for a task that waits, the number is limited by the external resource, and it is enforced with a semaphore. A pipeline is stages connected by channels, each closes its own output; so that stages do not leak when the consumer has left, every wait of a stage — both the receive and the send — sits in aselectwithctx.Done().Next they ask
11
Slices
A slice does not own its data — it is a window onto somebody else's array, and two windows can look at the same cells. That is why a write through one slice is visible in another, and why
appendsometimes writes into that same array and sometimes moves to a new one — which is exactly why it returns a slice whose result you assign back.Next they ask
- If
appendcan write into the same array, why does it return a slice at all? - Then why not always grow by doubling — surely that copies less?
- Is
copyreally faster thanappendin a loop? - What happens if you
appendto a slice inside a loop over that same slice? - Why does
capsometimes come out as a number nobody asked for? - Is a slice a reference type?
- If
12
Maps
A map holds key-value pairs addressed by key, and an absent key gives you not an error but the zero value; the
v, ok := m[k]form is what tells it apart from a genuine zero. Add as a second sentence what gets asked straight after: the variable holds a pointer to a runtime structure, which is why a map passed into a function changes for the caller, and why anilmap can be read but not written.Next they ask
- If a map is a pointer, why is
makeneeded at all? - Why can't you take the address of a map element?
- If a map never shrinks, how do you clean a long-lived cache?
- Why does a miss cost more than a hit — surely it is the other way round?
- Can keys be chosen so that a map degenerates into a list?
- When should you take
sync.Mapover a map with a mutex?
- If a map is a pointer, why is
13
Strings and runes
A string in Go is an immutable sequence of bytes, not of characters, and it need not hold valid UTF-8. So
lencounts bytes ands[i]yields a byte;for rangedecodes UTF-8 and hands back code points with byte offsets, and on an invalid byte it yieldsU+FFFDand advances by one byte. Add the layout separately and as a property of the implementation: in gc a string is a pointer and a length, a substring copies nothing and therefore retains the whole array, and+=in a loop copies what has accumulated every time, hence the quadratic work.Next they ask
14
Interfaces
An interface is a list of methods, and a type implements it silently, simply because it has those methods. Hence the two things asked next: a method with a pointer receiver is not in the method set of the value, and the interface variable itself carries a pair — a dynamic type and a dynamic value — that equals
nilonly when both halves are empty.Next they ask
- Can interfaces be compared with
==? - Where should an interface be declared — beside the implementation or at the consumer?
- What is the empty interface for and how does it differ from
any? - What is an
itaband when is it created? - How do you check at compile time that a type implements an interface?
- Interface over a value or over a pointer — what should go in?
- Can interfaces be compared with
15
Method receivers
A method with a value receiver works on a copy, while a method with a pointer receiver can change the original value. A receiver is the method's first argument written on the left, and arguments are copied; hence both "the method changed nothing" and "the type does not implement the interface".
Next they ask
- What if half a type's methods take a value and half take a pointer?
- Can a method be declared on a slice or a map?
- What does go vet say about copying a mutex?
- Can a method be called on a nil pointer?
- How does the receiver relate to the typed-nil trap?
- Is a pointer receiver worth it to "optimise" a small struct?
16
Embedding
Go has no inheritance. It has embedding — a field without a name whose fields and methods can be called directly;
s.Log()is shorthand fors.Logger.Log(). It is composition: the outer type contains the embedded one rather than being it, and a method of the embedded type works with its own value, not with the outer one.Next they ask
17
Errors
An error in Go is an ordinary value, returned and carried upwards by hand. The function returned it, the caller added context and passed it on, and one place at the top made the decision. Everything else in the topic is about whether adding that context kept a reference to the original error: with it the cause can be classified at the top, without it only the log text remains.
Next they ask
18
defer, panic, recover
A deferred call has three moments, not one. Registration and argument evaluation happen at the
defer, execution when the function exits; and if there are several deferred calls, they run in reverse order. A panic unwinds the stack, running each frame's deferred calls on the way, and it can be caught only by arecovercalled directly inside a deferred function.Next they ask
19
Context
A context carries cancellation, a deadline and request-scoped values down the call tree — so that work nobody wants any more can be stopped rather than carried to the end. Cancellation is a signal, not an interrupt: it works only where
Done()is read. And the link in the tree is one-way — cancellation travels down, never up and never sideways.Next they ask
20
The scheduler
A goroutine is not an operating-system thread but a unit of work belonging to the Go program itself. One word,
go, starts it; it costs kilobytes against a thread's megabytes; and which one is on a CPU right now is decided by a scheduler inside the process. That is why a program has thousands of goroutines with a handful of threads, and whyGOMAXPROCSlimits not how many goroutines exist but how many run at once.Next they ask
21
Channels
A channel is a link between two goroutines where the send and the receive meet: while there is no receiver the sender waits, and the other way round. A buffer does not remove that waiting, it defers it by exactly its capacity. A closed channel can be read — the buffered values are drained first; sending to it or closing it twice is not allowed. And
selectbranches are chosen uniformly at random, not top to bottom.Next they ask
- Who should close a channel, and why that side?
- How do you tell "a zero arrived" from "the channel is closed"?
- What buffer size should you pick?
- Channel or mutex — which do you take?
- What happens if every goroutine blocks on channels?
- Why does a
selectwith one ready branch and adefaultsometimes take thedefault?
22
sync and atomic
n++is three actions, not one — read, add, write; two goroutines wedge in between each other's steps and an update is lost. From there the choice is simple: a mutex protects the integrity of a region of code — any number of fields and any logic between them — while an atomic operation fits bounded state updated by a single indivisible operation: a counter, a flag, a pointer. Neither of them buys speed; both buy a guarantee that one goroutine sees another's work whole rather than half-done.Next they ask
23
Data races
A data race is when two goroutines access one memory location, at least one writes, and there is no synchronisation between the accesses. It is fixed with a mutex, a channel or an atomic operation. It is found with the
-raceflag:go test -race, and it should be in CI.Next they ask
24
Stack and heap
In Go you do not choose between the stack and the heap — the compiler chooses, and it asks one question: will the value outlive its frame. If that cannot be proved, it is the heap. Which is why returning a pointer to a local variable is legal, and why
&in the code says nothing about placement by itself.Next they ask
25
Garbage collector
The collector gives back the memory of objects with no path left to them from the roots, and it does that alongside the program — stopping it twice per cycle and briefly. Which is why the pause is short and does not depend on heap size: 16 and 256 megabytes of live data gave me 39 and 48 microseconds — 1.22× on a sixteenfold heap. What grows is not the pause but the work of marking — the share of CPU time, and that is exactly what gets confused with the pause.
Next they ask
- What is tri-colour marking and why is a write barrier needed?
- Why does runtime.GC() exist if the collector runs on its own?
- How do you tell that a program is spending its time collecting?
- Why is a process's RSS larger than what HeapAlloc reports?
- Can finalizers be relied on?
- What changes if you set GOGC=off?
26
Profiling
Take a CPU profile — from a benchmark with
go test -cpuprofileor from a service withnet/http/pprof— and open it ingo tool pprof -top. The profiler samples, so the functions where the program spends the most time end up at the top, while short calls may not get in at all. The rate — 100 times a second in go1.24.7 — is named as an implementation detail, not a property of the language.Next they ask
27
Testing
A test is a function
TestXxx(t *testing.T); a table-driven test is a slice of cases and one check in a loop; each case is at.Runsubtest and can be run on its own with-run.t.Parallelruns subtests in parallel, and shared resources must be released throught.Cleanup. Races are hunted withgo test -race, the cache is bypassed with-count=1.Next they ask
28
HTTP server
A handler is the
http.Handlerinterface,ServeMuxroutes requests by path, middleware is a functionHandler → Handler. The server serves each connection in its own goroutine (under HTTP/2, each stream), so mutable state shared by handlers must be protected. In production — your ownhttp.Serverwith timeouts and stopping throughShutdown.Next they ask