You have this code:
```go
func do() error {
var p *MyError // MyError implements error
if false {
p = &MyError{}
}
return p
}
func main() {
if err := do(); err != nil {
fmt.Println("got error")
} else {
fmt.Println("no error")
}
}
```
What prints, and why?
- A. "no error", because a nil *MyError returned as error is a nil interface
- B. "got error", because the returned interface holds a non-nil type descriptor even though its value pointer is nil ✓
- C. A compile error, because you cannot return a typed nil as an error interface
- D. A panic at runtime when err is compared to nil
Correct answer: B. An interface is a (type, value) pair; assigning a typed nil *MyError makes the type word non-nil, so the interface != nil even though the underlying pointer is nil.
In Go 1.21 (before the 1.22 loop-variable change), this runs:
```go
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func() { defer wg.Done(); fmt.Print(i) }()
}
wg.Wait()
```
What is the most accurate statement about its output?
- A. It always prints exactly "012" in order
- B. It reliably prints "333" every run
- C. It commonly prints "333" but the exact digits are unspecified because all goroutines share one i whose final value is 3 and scheduling is nondeterministic ✓
- D. It is a compile error because i is captured by reference
Correct answer: C. Pre-1.22 all closures capture the same i, which reaches 3 after the loop; output is nondeterministic but typically "333" since goroutines usually run after the loop completes.
Consider:
```go
a := []int{1, 2, 3, 4}
b := a[:2]
b = append(b, 99)
fmt.Println(a)
```
What does this print?
- A. [1 2 3 4]
- B. [1 2 99 4] ✓
- C. [1 2 99]
- D. [1 2 3 4 99]
Correct answer: B. b shares a's backing array with len 2 but cap 4, so append writes 99 into index 2 in place, overwriting the original 3.
A service shows steadily rising memory and `runtime.NumGoroutine()` climbing without bound. The suspect code:
```go
func fetch(ctx context.Context) int {
ch := make(chan int)
go func() { ch <- expensive() }()
select {
case v := <-ch:
return v
case <-ctx.Done():
return -1
}
}
```
What is the leak and the minimal fix?
- A. No leak; the goroutine is garbage collected once fetch returns
- B. On ctx cancellation the goroutine blocks forever on the unbuffered send; make ch buffered with capacity 1 so the send always completes ✓
- C. The leak is expensive() being slow; wrap it in a timeout instead
- D. Add defer close(ch) in fetch so the goroutine's send unblocks
Correct answer: B. When ctx.Done fires first, no one ever receives from the unbuffered ch, so the sender blocks permanently; a capacity-1 buffer lets the send complete and the goroutine exit.
Which statement about a nil pointer receiver is correct?
```go
type T struct{ v int }
func (t *T) Ping() string { return "pong" }
func (t *T) Val() int { return t.v }
```
- A. Calling Ping on a nil *T panics because the receiver is nil
- B. Calling Ping on a nil *T works, but calling Val on a nil *T panics when it dereferences t.v ✓
- C. Both Ping and Val panic on a nil *T
- D. Neither panics; t.v on a nil receiver returns the zero value safely
Correct answer: B. A method call on a nil pointer receiver is legal; it only panics if the body dereferences the pointer, so Ping is safe but Val's access to t.v faults.
Two goroutines run concurrently with no synchronization:
```go
m := map[int]int{}
go func() { for i := 0; i < 1000; i++ { m[i] = i } }()
go func() { for i := 0; i < 1000; i++ { m[i] = -i } }()
```
What is the defined behavior?
- A. The map ends up with some interleaved mix of values but never crashes
- B. The Go runtime may detect concurrent map writes and deliberately panic with "concurrent map writes" ✓
- C. It always produces a data-race compile error without -race
- D. Writes are serialized internally by a built-in map lock, so it is safe
Correct answer: B. Go maps are not concurrency-safe and the runtime intentionally detects concurrent access, calling fatal("concurrent map writes") to fail loudly rather than corrupt silently.
What does this print?
```go
func f() (result int) {
defer func() { result *= 2 }()
result = 5
return 10
}
fmt.Println(f())
```
Correct answer: B. return 10 sets the named return value result to 10, then the deferred closure runs and doubles it to 20 before the function actually returns.
You need a cache read heavily, written rarely, with keys of one type, from many goroutines. For lowest overhead in the read-dominated steady state, which choice is best and why?
- A. sync.Map, because it is always faster than a mutex-guarded map
- B. A plain map guarded by sync.Mutex, because mutexes are cheaper than atomics
- C. A plain map guarded by sync.RWMutex, allowing concurrent readers while still serializing the rare writes ✓
- D. An unsynchronized map, since rare writes make races unlikely
Correct answer: C. An RWMutex lets many readers proceed in parallel and only blocks for the infrequent writes, which fits a read-heavy homogeneous-key workload better than the interface-boxing and dual-store overhead of sync.Map.
Regarding escape analysis, which scenario forces the value to be heap-allocated?
```go
// A
func a() int { x := 42; return x }
// B
func b() *int { x := 42; return &x }
// C
func c() { x := 42; _ = x }
```
- A. A, because returning a value copies it to the heap
- B. B, because the address of x outlives the stack frame so it must escape to the heap ✓
- C. C, because unused locals are heap-allocated
- D. None; Go always stack-allocates locals declared with :=
Correct answer: B. Returning &x means the pointer outlives b's frame, so escape analysis moves x to the heap; a and c keep x on the stack.
In a select with multiple ready cases:
```go
select {
case <-a:
// ...
case <-b:
// ...
default:
// ...
}
```
Which statement is precisely correct?
- A. Cases are evaluated top-to-bottom, so a wins whenever both a and b are ready
- B. If both a and b are ready, one is chosen uniformly at random; default runs only when neither a nor b is ready ✓
- C. default is chosen whenever it is present, making the select non-blocking always
- D. The select blocks until all channels are ready
Correct answer: B. When several non-default cases are ready select picks one at pseudo-random (no priority), and the default clause only executes when no other case can proceed immediately.
What does GOMAXPROCS control?
- A. The maximum number of goroutines
- B. The number of OS threads executing Go code simultaneously ✓
- C. The default goroutine stack size
- D. The number of GC worker threads
Correct answer: B. GOMAXPROCS sets how many Ps (and thus OS threads) can run Go code in parallel.
How do goroutine stacks behave regarding size?
- A. Fixed at 1 MB each
- B. Start small and grow or shrink dynamically ✓
- C. Share a single main stack
- D. Fully preallocated on the heap
Correct answer: B. Goroutine stacks begin small (a few KB) and are resized dynamically as needed.
What does a select statement do when no case is ready and there is no default?
- A. It panics
- B. It returns immediately
- C. It blocks until a case becomes ready ✓
- D. It busy-loops spinning
Correct answer: C. Without a default, select blocks until one of its communications can proceed.
How does the Go race detector find data races?
- A. Purely static source analysis
- B. Runtime instrumentation of memory accesses ✓
- C. Inspecting held mutexes only
- D. Compile-time type checking
Correct answer: B. The race detector instruments memory accesses at runtime to observe conflicting unsynchronized access.
When a context is cancelled, what happens to its Done channel?
- A. It receives a cancellation value
- B. It is closed ✓
- C. It is set to nil
- D. It is garbage collected
Correct answer: B. Cancelling a context closes its Done channel, unblocking all receivers.
Appending to a slice can mutate the original backing array when?
- A. len equals cap
- B. len is less than cap ✓
- C. Always, regardless of capacity
- D. Never under any condition
Correct answer: B. If len < cap, append writes into the existing backing array shared with other slices.
What is true about copying a sync.Mutex value after it has been used?
- A. It is always safe
- B. It yields an independent lock that breaks the mutex invariants ✓
- C. It performs a deep copy of the lock state atomically
- D. The compiler prevents it entirely
Correct answer: B. Copying a Mutex duplicates its internal state, producing separate locks that no longer provide mutual exclusion.
What does the Go compiler's escape analysis determine?
- A. Whether a variable lives on the stack or the heap ✓
- B. Whether a goroutine can be inlined
- C. Which variables become GC roots
- D. Whether values fit in CPU registers
Correct answer: A. Escape analysis decides whether a value can stay on the stack or must be heap-allocated.
Which best describes Go's garbage collector?
- A. Generational copying collector
- B. Reference counting
- C. Concurrent tri-color mark-and-sweep ✓
- D. Stop-the-world compacting collector
Correct answer: C. Go uses a concurrent, non-generational tri-color mark-and-sweep collector.
When does iota reset to 0?
- A. At the start of each new line
- B. At the start of each const block ✓
- C. Once at package initialization
- D. It never resets, only increments
Correct answer: B. iota resets to 0 whenever a new const declaration block begins and increments per ConstSpec within it.
In Go's memory model, what makes one goroutine's writes visible to another?
- A. Sharing the variable alone
- B. Channel operations or sync primitives that establish happens-before ✓
- C. Calling time.Sleep between them
- D. Relying on goroutine scheduling
Correct answer: B. Visibility requires a happens-before edge, created by channel ops, mutexes, or other sync primitives.
What is a typical cause of a goroutine leak?
- A. A goroutine blocked forever on a channel that never proceeds ✓
- B. Using defer inside a loop
- C. Using a WaitGroup
- D. Closing a channel
Correct answer: A. A goroutine permanently blocked on a channel send/receive is never collected, leaking memory.
What does receiving from a closed channel yield?
- A. A panic
- B. It blocks forever
- C. The zero value with ok=false once drained ✓
- D. The last value repeated
Correct answer: C. After buffered values are drained, receives on a closed channel return the zero value with ok=false without blocking.
What happens if you send to a closed channel?
- A. It blocks
- B. It returns the zero value
- C. It is a no-op
- D. It panics ✓
Correct answer: D. Sending on a closed channel causes a runtime panic.
What does Go's garbage collector write barrier primarily enable?
- A. Concurrent marking while mutators keep running ✓
- B. Faster memory allocation
- C. Automatic stack shrinking
- D. Escape analysis
Correct answer: A. The write barrier tracks pointer writes so the GC can mark concurrently without long stop-the-world pauses.
What is escape analysis in the Go compiler?
- A. Detecting goroutine leaks
- B. Deciding whether a variable can stay on the stack or must go to the heap ✓
- C. Analyzing panic propagation
- D. Unrolling loops
Correct answer: B. Escape analysis determines if a value's lifetime escapes its function, requiring heap allocation instead of the stack.
What happens if sync.WaitGroup.Done() is called more times than Add()?
- A. It panics because the counter goes negative ✓
- B. It blocks
- C. It is a no-op
- D. It resets the counter to zero
Correct answer: A. Decrementing the WaitGroup counter below zero triggers a panic.
How do the method sets of value type T and pointer *T differ?
- A. *T's method set includes pointer-receiver methods; T's does not ✓
- B. Both method sets are identical
- C. T includes pointer-receiver methods
- D. Neither includes value-receiver methods
Correct answer: A. A *T method set contains both value- and pointer-receiver methods, while T's set contains only value-receiver methods.
What does cancelling a context.Context propagate to?
- A. Its derived child contexts and their Done channels ✓
- B. Only the parent context
- C. Every goroutine in the program
- D. The garbage collector
Correct answer: A. Cancellation flows down to all contexts derived from it, closing their Done channels.