Deep Engineering

Go · Interview preparation

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.

  1. 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.Clone with maps.Clone do 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.

    Full answer in the lesson →

    Next they ask

  2. 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.

    Full answer in the lesson →

    Next they ask

  3. 03

    Reflection

    Reflection in Go is the reflect package, 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 through Int(), String() and the like, but not through Interface(). Set and SetInt work only if CanSet of the specific Value is true: 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 call Elem(). A slice element and a map entry are reachable even without a pointer — Index and SetMapIndex have their own conditions. encoding/json, fmt and 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.

    Full answer in the lesson →

    Next they ask

  4. 04

    io.Reader and io.Writer

    io.Reader is an interface with one method Read(p []byte) (n int, err error): it puts up to len(p) bytes into p and returns how many it put, plus an error. io.Writer is the same for writing, but having written less than len(p) it must return an error, and the error is checked even after a full write. When reading, you handle the n bytes first and then look at err, and io.EOF is the normal end of a stream. For ready-made tasks — io.Copy, io.ReadFull, bufio.

    Full answer in the lesson →

    Next they ask

  5. 05

    Go tooling

    go build compiles and leaves a binary, go run builds 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. gofmt puts code into the one canonical format, and nobody argues with it. go vet catches errors the compiler lets through — a wrong Printf verb, a copied mutex, unreachable code; go test runs only part of its checks, so go vet ./... in CI is a separate step.

    Full answer in the lesson →

    Next they ask

  6. 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 with nil. 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.

    Full answer in the lesson →

    Next they ask

  7. 07

    Packages and layout

    A package is a directory of files with one name in the package line. 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 all init() functions in file order; and only then main.

    Full answer in the lesson →

    Next they ask

  8. 08

    Modules and dependencies

    A module is a set of packages released under one version; the go.mod at its root lists requirements — a module and a version "no lower than" — and go.sum stores the hashes of the archive and of go.mod for 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.

    Full answer in the lesson →

    Next they ask

  9. 09

    JSON and tags

    Exported fields go into JSON, the key is the field name or the json:"…" tag. omitempty skips false, 0, "", nil and empty slices, maps and arrays of length zero, but not structs — which is why time.Time{} comes out as "0001-01-01T00:00:00Z". Unmarshal matches fields without regard to case, skips extra keys silently and does not touch fields absent from the input.

    Full answer in the lesson →

    Next they ask

  10. 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 than GOMAXPROCS workers 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 a select with ctx.Done().

    Full answer in the lesson →

    Next they ask

  11. 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 append sometimes 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.

    Full answer in the lesson →

    Next they ask

  12. 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 a nil map can be read but not written.

    Full answer in the lesson →

    Next they ask

  13. 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 len counts bytes and s[i] yields a byte; for range decodes UTF-8 and hands back code points with byte offsets, and on an invalid byte it yields U+FFFD and 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.

    Full answer in the lesson →

    Next they ask

  14. 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 nil only when both halves are empty.

    Full answer in the lesson →

    Next they ask

  15. 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".

    Full answer in the lesson →

    Next they ask

  16. 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 for s.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.

    Full answer in the lesson →

    Next they ask

  17. 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.

    Full answer in the lesson →

    Next they ask

  18. 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 a recover called directly inside a deferred function.

    Full answer in the lesson →

    Next they ask

  19. 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.

    Full answer in the lesson →

    Next they ask

  20. 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 why GOMAXPROCS limits not how many goroutines exist but how many run at once.

    Full answer in the lesson →

    Next they ask

  21. 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 select branches are chosen uniformly at random, not top to bottom.

    Full answer in the lesson →

    Next they ask

  22. 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.

    Full answer in the lesson →

    Next they ask

  23. 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 -race flag: go test -race, and it should be in CI.

    Full answer in the lesson →

    Next they ask

  24. 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.

    Full answer in the lesson →

    Next they ask

  25. 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.

    Full answer in the lesson →

    Next they ask

  26. 26

    Profiling

    Take a CPU profile — from a benchmark with go test -cpuprofile or from a service with net/http/pprof — and open it in go 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.

    Full answer in the lesson →

    Next they ask

  27. 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 a t.Run subtest and can be run on its own with -run. t.Parallel runs subtests in parallel, and shared resources must be released through t.Cleanup. Races are hunted with go test -race, the cache is bypassed with -count=1.

    Full answer in the lesson →

    Next they ask

  28. 28

    HTTP server

    A handler is the http.Handler interface, ServeMux routes requests by path, middleware is a function Handler → 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 own http.Server with timeouts and stopping through Shutdown.

    Full answer in the lesson →

    Next they ask

← Track map and final check