Go Developer interview prep
A spaced-repetition deck of 333+ Go Developer interview questions — organised by topic and difficulty, and scheduled for review based on your ratings. Preview a few cards below, then choose access to study the whole track on an Anki-style SM-2 schedule.
Eligible new monthly or yearly subscribers get 7 days free. Checkout confirms eligibility and charges.
Before you start
Start with basic Go syntax, functions, and structs. Go modules are combined with shared SQL, infrastructure, system design, and backend engineering; some shared examples use Python and shell commands.
First pass: Language Basics: Slices, Maps & Strings → Interfaces & Type System. Begin with the foundational questions in these modules, then follow the outline. Revisit advanced and senior questions as the job requires; all cards remain available from the start.
Shared topics can appear in several tracks and keep one review history. The outline labels shared foundations and role-specific modules; choose your depth for the job you are preparing for.
What's covered
Every topic in this track, grouped the way you'd study it.
Language Basics: Slices, Maps & Strings
15 cardsRole-specific module
Interfaces & Type System
6 cardsRole-specific module
Errors, defer & panic
7 cardsRole-specific module
Goroutines, Channels & select
12 cardsRole-specific module
sync, atomic & context
12 cardsRole-specific module
Scheduler, GC & Memory
8 cardsRole-specific module
Concurrency Patterns (Live Coding)
8 cardsRole-specific module
Testing, Tooling & Generics
7 cardsRole-specific module
Databases
104 cardsShared foundations
DevOps & Infra
35 cardsShared foundations
System Design
29 cardsShared foundations
Distributed Systems
12 cardsShared foundations
Kafka & Messaging
12 cardsShared foundations
Caching Deep-Dive
10 cardsShared foundations
Performance & Concurrency
11 cardsShared foundations
Reliability & Incidents
10 cardsShared foundations
Behavioral
35 cardsShared foundations
Sample questions
A few cards from the deck — reveal each answer, then choose access to study the full set on a schedule.
How does a slice differ from an array, and what does a slice look like under the hood?
How does a slice differ from an array, and what does a slice look like under the hood?
Short answer: An array ([N]T) contains a fixed number of elements; its length is part of its type. Assigning an array copies its elements. A slice ([]T) describes a window into a backing array, so assigning a slice shares that array.
In depth:
- Think of a slice as
{pointer, len, cap}: the pointer identifies its first element,lenis the visible element count, andcapis how far it can grow from that position without needing another array. arr[1:3]includes indexes 1 and 2. The upper bound is excluded. It has length 2 and, for a five-element array, capacity 4.- The standard implementation uses three machine words for a slice descriptor: typically 24 bytes on 64-bit systems and 12 on 32-bit systems. That layout is an implementation detail, not a reason to hard-code its size.
arr := [5]int{0, 1, 2, 3, 4}
s := arr[1:3] // [1 2], len=2, cap=4
copyOfArray := arr
alias := s
alias[0] = 9
fmt.Println(arr, copyOfArray, s)
// [0 9 2 3 4] [0 1 2 3 4] [9 2]
Watch out: Copying an array copies its elements, not the objects that pointer, map, or slice elements refer to. Both array and slice storage placement depends on compiler escape analysis.
Sources: Go reference 1, Go reference 2.
How does Go achieve polymorphism without classes and inheritance?
How does Go achieve polymorphism without classes and inheritance?
Short answer: An interface names a behavior through method signatures. Any type with the required method set implements it implicitly. A function accepting that interface can work with multiple concrete types, and a method call uses the implementation belonging to the stored dynamic value.
type Speaker interface{ Speak() string }
type Cat struct{}
type Dog struct{}
func (Cat) Speak() string { return "meow" }
func (Dog) Speak() string { return "woof" }
func announce(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
announce(Cat{}) // meow
announce(Dog{}) // woof
}
In depth: The signatures must match; no implements declaration is needed. Interfaces are checked statically at assignment/call sites, while calls through interface values can dispatch dynamically. Prefer small interfaces describing what the consumer needs.
Embedding is a separate tool: it composes one type inside another and can promote its methods. It does not make the outer type a subtype of the embedded type or introduce class-style method overriding. Package-level encapsulation is controlled by exported names, which begin with an uppercase Unicode letter.
Watch out: “Go has no class inheritance” does not mean “Go has no dynamic dispatch.” Interface calls provide it. Embedding alone does not make a method on an inner value dispatch to a same-named outer method.
Sources: Go reference 1, Go reference 2.
When should a function return an error, and when should it panic?
When should a function return an error, and when should it panic?
Short answer: Return an error for failures callers are expected to handle, such as invalid input, missing files, timeouts, or unavailable dependencies. Reserve panic for programmer errors, violated invariants, or a documented API that intentionally panics.
Examples: A parser returns an error for malformed user input. A MustParse helper may intentionally panic for a fixed developer-controlled value during initialization. The runtime also panics for some invalid operations, such as an out-of-range index.
A panic unwinds deferred calls in the current goroutine. If it reaches that goroutine's top without recovery, the process terminates. Recovery at a deliberate boundary can contain a failure—an HTTP server may recover a handler panic—but it must not pretend the failed operation succeeded or assume shared state is intact.
Do not equate severity with mechanism: An ordinary returned error can still be fatal to an application. A panic does not prove that every piece of process state is corrupt. Choose the API contract and recovery boundary based on what can safely continue; do not add blanket recover to every goroutine or use panic for routine validation.
Further reading: Go documentation · Go documentation
How does a goroutine differ from an OS thread?
How does a goroutine differ from an OS thread?
Short answer: A goroutine is a concurrent execution managed by the Go runtime. An OS thread is scheduled by the operating system. Go multiplexes many goroutines over OS threads, and each goroutine has a stack that can grow and shrink.
In depth:
- Start one with
go f(). The caller continues without waiting; add an explicit synchronization point when completion matters. - Goroutine stacks start small (historically around 2 KiB in the standard runtime). The initial size and growth policy are implementation details. OS-thread stack sizes depend on the platform and configuration.
- When a goroutine blocks on a channel or supported network I/O, the runtime can run another goroutine on the available thread. Blocking system calls and cgo can require additional OS threads.
- Runtime scheduling is generally cheaper than creating and managing a dedicated OS thread per task. Exact costs and safe counts depend on the workload; goroutines still consume memory and retain referenced objects.
done := make(chan struct{})
go func() {
fmt.Println("work")
close(done)
}()
<-done // wait for this goroutine's completion
Watch out: Concurrency means tasks can make progress independently; parallelism means they run simultaneously. Goroutines provide concurrency, while available CPUs and GOMAXPROCS constrain parallel Go execution. Returning from main ends the program without waiting for other goroutines.
Sources: Go reference 1, Go reference 2.
What's the difference between sync.Mutex and sync.RWMutex, and when is RWMutex actually worth it?
What's the difference between sync.Mutex and sync.RWMutex, and when is RWMutex actually worth it?
Short answer: Mutex allows one critical section at a time. RWMutex permits concurrent readers through RLock, while Lock excludes both readers and other writers. Start with the simpler lock; choose RWMutex when measurement shows that parallel reads help.
How to reason about it: All accesses to the protected invariant must use the same lock. Under RLock, only read the shared data: updating an LRU position or lazily initializing a field is a write. An RWMutex cannot be upgraded from RLock to Lock, and recursive read locking can deadlock when a writer is waiting.
A pending writer prevents new readers from acquiring the lock until that writer has acquired and released it. This avoids one source of writer starvation; it does not promise scheduling fairness or a maximum wait.
Example: A configuration lookup that performs substantial read-only work may benefit from concurrent readers. A tiny counter increment often does not. There is no universal read/write ratio or performance ranking—benchmark representative contention.
Further reading: Go documentation
What does GOMAXPROCS control, and what's its default?
What does GOMAXPROCS control, and what's its default?
Short answer: GOMAXPROCS limits how many operating-system threads can execute Go code simultaneously. It does not limit the number of goroutines, blocked syscall threads, or concurrent I/O operations.
Default by version: Before Go 1.25, the default was the logical CPU count available at startup. Go 1.25+ can also consider CPU affinity and, on Linux, cgroup CPU bandwidth limits, and periodically update the default. Compatibility settings associated with older module language versions can retain the old behavior.
The current runtime normally uses the minimum of CPU count, affinity count, and rounded-up cgroup quota, but does not go below two solely because of the quota. This calculation is an implementation detail: do not equate GOMAXPROCS with Kubernetes CPU requests or simply with runtime.NumCPU().
n := runtime.GOMAXPROCS(0) // query without changing it
fmt.Println(n)
Setting the environment variable or calling runtime.GOMAXPROCS(n) with a positive value overrides automatic updates. Go 1.25+ provides runtime.SetDefaultGOMAXPROCS() to restore the runtime default and updates. Check the toolchain, module version, and GODEBUG settings before diagnosing container throttling.
Further reading: Go documentation · Go documentation
Ready to make it stick?
Explore a sample, choose a track, and build a review routine that fits your preparation.
Questions about this track
How should I prepare for a Go Developer interview?
Study the concepts you'll be asked to explain, not just the ones you can code. RecallDeck's Go Developer track gives you 333+ curated interview questions and resurfaces each one with an Anki-style SM-2 schedule based on your ratings — to practise recalling and explaining them. Combine reviews with coding and mock interviews.
What topics does the Go Developer track cover?
The Go Developer track is organised into the core areas Go Developer interviews actually test, grouped by topic and by difficulty (Concept, Junior, Middle, Senior). You can preview the full outline and sample questions above before signing in.
Is spaced repetition effective for Go Developer interview prep?
Yes. Active recall and spaced practice can help retain what you study. Grade yourself honestly and check your understanding with practical work. RecallDeck schedules each Go Developer card using your ratings, so familiar cards return less often and difficult ones get more practice.
Can I try the Go Developer track before paying?
Eligible new monthly or yearly subscribers can try the complete Go Developer track and every feature for seven days. Checkout confirms trial eligibility and the amount due; returning subscribers may be charged immediately. Cancel online before the trial ends to avoid its first charge. Lifetime access has no trial.