Interviewer desk sheet
Intermediate Go
in 10 questions
Five struct/interface questions. Five concurrency questions. Four minutes each. Score prediction + model + safe fix.
Opening script
“I’ll show short Go scenarios. First predict what happens and explain why; then suggest a safe design. It’s fine to think aloud. I care more about your model and trade-offs than exact terminology.”
3 minSet expectations
20 minStructs + interfaces
20 minConcurrency
7 minCandidate questions
Question bank
Ask → listen → probe
Structs & interfaces
| # | Ask | Must hear for 2 points | Probe / red flag |
|---|---|---|---|
| 1 | c.Add(), Incrementer=c, Incrementer=&c: which compile when Add has a pointer receiver? |
Call / assignment / assignment = yes / no / yes. Addressable-call shorthand differs from method-set satisfaction. | Probe: mutating receiver? Red: “pointer methods work on both.” |
| 2 | Copy a struct containing a slice and map, then mutate elements through the copy. What changes in the original? | Struct fields are copied, but slice backing storage and map data remain shared. Deep-copy referenced data for independence. | Probe: append and capacity? Red: “structs are references” or “always deep copied.” |
| 3 | C embeds A and B; both define Ping. Does C implement interface{ Ping() }? |
No: same-depth promotion is ambiguous. Qualify the field or declare C.Ping. Embedding is composition, not inheritance. |
Probe: add C.Ping? Red: subclass/override language. |
| 4 | A function returns error containing (*AppError)(nil). Is the returned interface nil? |
No. It contains dynamic type *AppError and dynamic value nil. Return explicit nil on success. |
Probe: method call through it? Red: reflection as the normal fix. |
| 5 | An encoder only writes bytes. Accept *bytes.Buffer, a broad custom interface, or io.Writer? |
io.Writer: smallest established consumer need. Don’t require or invoke lifecycle methods the function does not own. |
Probe: constructor return type? Red: speculative methods or closing caller-owned writer. |
Concurrency
| # | Ask | Must hear for 2 points | Probe / red flag |
|---|---|---|---|
| 6 | A goroutine writes n=1; main immediately prints n. What happens? |
Data race; no guaranteed value. Starting a goroutine does not join it. Use a channel or WaitGroup to order completion. | Probe: is sleep a fix? Red: deterministic answer based on timing. |
| 7 | Same goroutine sends twice to capacity-one channel, then receives. What happens? | Second send blocks; deadlock. Buffer decouples only up to capacity and then applies backpressure. Capacity two is brittle here. | Probe: meaningful cap-one cases? Red: “make buffer huge.” |
| 8 | Many workers send to one results channel. Who closes it, and when? | Sender-side coordinator closes after wg.Wait, while consumer drains. Closed receive / send / nil semantics are clear. |
Probe: why coordinator goroutine? Red: every worker closes output. |
| 9 | A value-receiver method locks a mutex field and writes a map field. Why unsafe? | Receiver copies mutex while map storage remains shared. Use pointer receiver; never copy mutex after use; lock all accesses. | Probe: RWMutex trade-off? Red: focuses only on defer cost. |
| 10 | Producer sends forever; caller receives one value then returns. What remains? | Producer leaks on a blocked send. Propagate context/done, select sends against cancellation, sender closes output. | Probe: blocking inner call? Red: buffer or GC as lifecycle control. |
Full code, explanations, and interactive scoring: open the lesson.
Fast facts
The mental model at a glance
Types
Tmethod set: methods onT.*Tmethod set: methods onTand*T.- Interface nil only when dynamic type and value are both absent.
- Embedding promotes unambiguous selectors; it is not inheritance.
- Struct assignment copies fields; reference-like fields may still alias.
Coordination
- Channel: values, signals, ownership transfer, ordering.
- Mutex: one shared invariant, one consistent lock rule.
- WaitGroup: completion only.
- Context: cancellation/deadline through an operation tree.
- Goroutine: must have a stop condition and observable completion when needed.
Closed channel
Receive drains buffered values, then returns zero value and ok=false. Send panics. Closing again panics.
Nil channel
Send and receive block forever. In select, a nil-channel case is disabled.
Race check
go test -race ./... detects races only on runtime paths actually exercised.
Close well
Before deciding
- Separate missing terminology from missing reasoning.
- Accept an alternative design when ownership, lifetime, and synchronization are sound.
- Record one concrete example supporting every strong or weak rating.
- Do not convert the 20-point total into an automatic hiring decision.