Go Backend Developer Interview Questions: Practical Guide

Milad Bonakdar
Author
Prepare for Go backend interviews with focused questions on goroutines, channels, context, error handling, testing, HTTP servers, and system design.
Introduction
Go backend interviews usually test how you write simple, reliable services: goroutines and channels, context cancellation, explicit error handling, testing, HTTP middleware, and design trade-offs. Strong answers explain not only what a feature does, but when you would avoid it.
Use this guide to practice concise answers, then connect each topic to a real project: an API you built, a worker pool you debugged, a race condition you fixed, or a service you shut down safely during deploys.
Core Go Concepts
1. What makes Go different from other languages like Java or Python?
Answer: Go was designed for readable, maintainable systems code. A practical interview answer should mention:
- Simplicity: Go avoids inheritance and method overloading, so teams rely on composition, small interfaces, and clear package boundaries.
- Concurrency: Goroutines, channels,
syncprimitives, andcontextmake concurrent backend work explicit. - Compilation: Go builds native binaries with fast startup and straightforward deployment.
- Standard library: Packages such as
net/http,context,encoding/json, andtestingcover a lot of everyday backend work before you add frameworks.
Rarity: Common Difficulty: Easy
2. Explain the difference between Arrays and Slices.
Answer:
- Arrays: Fixed-size sequences of elements of the same type. The size is part of the type (e.g.,
[5]intis different from[10]int). They are value types; assigning one array to another copies all elements. - Slices: Dynamic, flexible views into an underlying array. They consist of a pointer to the array, a length, and a capacity. Slices are reference-like; passing a slice to a function allows modification of the underlying elements without copying the entire data.
Rarity: Common Difficulty: Easy
3. How do Interfaces work in Go? What is implicit implementation?
Answer: Interfaces in Go are collections of method signatures.
- Implicit Implementation: Unlike Java or C#, a type does not explicitly declare that it implements an interface (no
implementskeyword). If a type defines all the methods declared in an interface, it automatically implements that interface. - Duck Typing: "If it walks like a duck and quacks like a duck, it's a duck." This decouples definition from implementation, making code more flexible and easier to mock for testing.
Rarity: Common Difficulty: Medium
4. What is the defer keyword and how does it work?
Answer:
defer schedules a function call to be run immediately before the function returns. It is commonly used for resource cleanup, such as closing files, unlocking mutexes, or closing database connections.
- LIFO Order: Deferred calls are executed in Last-In-First-Out order.
- Arguments Evaluation: Arguments to deferred functions are evaluated when the
deferstatement is executed, not when the call runs.
Example:
Rarity: Common Difficulty: Easy
Concurrency
5. Explain Goroutines and how they differ from OS threads.
Answer:
- Goroutines: Lightweight units of execution managed by the Go runtime. Their stacks start small and grow as needed, so they are cheaper than OS threads but still need lifecycle control.
- OS threads: Managed by the kernel and more expensive to create and switch.
- Scheduler: The Go runtime multiplexes many goroutines over OS threads. In backend code, mention how you stop goroutines with
context, drain channels, and avoid leaks.
Rarity: Very Common Difficulty: Medium
6. What are Channels? Buffered vs. Unbuffered?
Answer: Channels are typed conduits that allow goroutines to communicate and synchronize execution.
- Unbuffered Channels: Have no capacity. A send operation blocks until a receiver is ready, and vice versa. They provide strong synchronization.
- Buffered Channels: Have a capacity. A send operation only blocks if the buffer is full. A receive operation blocks only if the buffer is empty. They decouple the sender and receiver to some extent.
Rarity: Common Difficulty: Medium
7. How do you handle Race Conditions in Go?
Answer: A race condition occurs when multiple goroutines access shared memory concurrently, and at least one access is a write.
- Detection: Use the built-in race detector with
go test -race,go run -race, or a race-enabled binary under realistic load. It only finds races on executed code paths. - Prevention:
- Own the data: Prefer one goroutine owning mutable state and receiving commands over a channel.
- Use locks deliberately: Protect shared maps and structs with
sync.Mutexorsync.RWMutex. - Use atomics narrowly: Reach for
sync/atomicfor simple counters or flags, not complex invariants.
Rarity: Common Difficulty: Hard
8. What is the select statement used for?
Answer:
The select statement lets a goroutine wait on multiple communication operations. It blocks until one of its cases can run, then it executes that case. If multiple are ready, it chooses one at random.
- Timeouts: Can be implemented using
time.After. - Non-blocking Operations: A
defaultcase makes the select non-blocking if no other case is ready.
Example:
Rarity: Medium Difficulty: Medium
Error Handling & Robustness
9. How does Error Handling work in Go?
Answer:
Go treats errors as values. Functions return an error type (usually as the last return value) instead of throwing exceptions.
- Check Errors: Callers must explicitly check if the error is
nil. - Custom Errors: You can create custom error types by implementing the
errorinterface (which has a singleError() stringmethod). - Wrapping: Go 1.13 introduced error wrapping (
fmt.Errorf("%w", err)) to add context while preserving the original error for inspection (usingerrors.Isanderrors.As).
Rarity: Common Difficulty: Easy
10. What are Panic and Recover? When should you use them?
Answer:
- Panic: Stops the ordinary flow of control and begins panicking. It's similar to an exception but should be reserved for unrecoverable errors (e.g., nil pointer dereference, index out of bounds).
- Recover: A built-in function that regains control of a panicking goroutine. It is only useful inside a
deferfunction. - Usage: Generally discouraged for normal control flow. Use
errorvalues for expected error conditions. Panic/recover is mostly used for truly exceptional situations or inside libraries/frameworks to prevent a crash from bringing down the entire server.
Rarity: Medium Difficulty: Medium
System Design & Backend
11. How would you structure a Go web application?
Answer: Go does not require one universal layout, so interviewers usually want to hear a simple structure that fits the service size:
/cmd: command entry points, useful when a repo builds more than one binary./internal: private application packages; the Go toolchain prevents imports from outside the parent tree./api: OpenAPI specs, protobuf files, or contract definitions when the service exposes them.- Handlers, services, repositories: Keep HTTP transport, business logic, and persistence concerns separate enough to test.
- Avoid ceremony: Do not add a large framework-style layout before the code needs it.
Rarity: Common Difficulty: Medium
12. How does the context package work and why is it important?
Answer:
The context package carries deadlines, cancellation signals, and request-scoped values across API boundaries and goroutines.
- Cancellation: If a client disconnects or a parent operation is canceled, downstream work should observe
ctx.Done()and stop. - Timeouts: Use
context.WithTimeoutorcontext.WithDeadlinearound database queries and external calls, and call the returned cancel function, often withdefer cancel(). - Values: Store only request-scoped metadata, not optional function parameters or large objects.
Rarity: Very Common Difficulty: Hard
13. What is Dependency Injection and how is it done in Go?
Answer: Dependency Injection (DI) is a design pattern where an object receives other objects that it depends on.
- In Go: Usually implemented by passing dependencies (like a database connection or a logger) into a struct's constructor or factory function, often via interfaces.
- Benefits: Makes code more modular and testable (easy to swap real DB with a mock).
- Frameworks: While manual DI is preferred for simplicity, libraries like
google/wireoruber-go/digexist for complex graphs.
Rarity: Medium Difficulty: Medium
Database & Tools
14. How do you handle JSON in Go?
Answer:
Go uses the encoding/json package.
- Struct Tags: Use tags like
`json:"field_name"`to map struct fields to JSON keys. - Marshal: Converts a Go struct to a JSON string (byte slice).
- Unmarshal: Parses JSON data into a Go struct.
- Streaming:
json.Decoderandjson.Encoderare better for large payloads as they process streams of data.
Rarity: Common Difficulty: Easy
15. What are some common Go tools you use?
Answer:
go mod: Dependency management.go fmt: Formats code to standard style.go vet: Examines code for suspicious constructs.go test: Runs tests and benchmarks.pprof: Profiling tool for analyzing CPU and memory usage.delve: Debugger for Go.
Rarity: Common Difficulty: Easy
Advanced Topics & Best Practices
16. What are Generics in Go and when should you use them?
Answer: Generics (introduced in Go 1.18) allow you to write functions and data structures that work with any of a set of types, rather than a specific type.
- Type Parameters: Defined using square brackets
[]. e.g.,func Map[K comparable, V any](m map[K]V) ... - Constraints: Interfaces that define the set of permissible types (e.g.,
any,comparable). - Usage: Use generics when the same algorithm or data structure is truly type-independent, such as sets, helpers, or collection utilities. Start with ordinary code; add type parameters when duplication becomes real. If a small interface communicates the behavior better, prefer the interface.
Rarity: Common Difficulty: Medium
17. Explain Table-Driven Tests in Go.
Answer: Table-driven testing is a preferred pattern in Go where test cases are defined as a slice of structs (the "table"). Each struct contains the input arguments and the expected output.
- Benefits:
- Clean separation of test logic and test data.
- Easy to add new test cases (just add a row to the table).
- Clear failure messages showing exactly which input failed.
- Example:
Rarity: Common Difficulty: Easy
18. What is the Middleware Pattern in Go HTTP servers?
Answer:
Middleware is a function that wraps an http.Handler to perform pre- or post-processing logic before passing control to the next handler.
- Signature:
func(next http.Handler) http.Handler - Use Cases: Logging, Authentication, Panic Recovery, Rate Limiting, CORS.
- Chaining: Middleware can be chained together (e.g.,
Log(Auth(Handler))).
Example:
Rarity: Very Common Difficulty: Medium
19. How do you implement Graceful Shutdown in a Go server?
Answer: Graceful shutdown ensures that a server stops accepting new requests but finishes processing active requests before exiting.
- Mechanism:
- Listen for OS signals (SIGINT, SIGTERM) using
os/signal. - Create a
context.WithTimeoutto allow a cleanup window (e.g., 5-10 seconds). - Call
server.Shutdown(ctx)on thehttp.Server. - Close DB connections and other resources.
- Listen for OS signals (SIGINT, SIGTERM) using
- Importance: Prevents data loss and client errors during deployments.
Rarity: Common Difficulty: Hard
20. When should you use sync.Map instead of a regular map with a Mutex?
Answer:
sync.Map is a concurrent-safe map implementation in the standard library.
- Use Cases:
- Cache Contention: When the entry for a given key is only ever written once but read many times (e.g., lazy-loading caches).
- Disjoint Keys: When multiple goroutines read, write, and overwrite entries for disjoint sets of keys.
- Trade-off: For general use cases (frequent read/write updates), a regular
mapprotected by async.RWMutexis often faster and has better type safety (sincesync.Mapusesany).
Rarity: Uncommon Difficulty: Hard


