RMRM Full Stack & AI Engineer · All questions · Roadmaps
Languages · interview questions

Go Interview Questions

Go (Golang) is a statically-typed, compiled language developed by Google, known for simplicity, fast compilation, built-in concurrency via goroutines and channels, and strong standard library. These questions cover core language features, concurrency, memory management, and idiomatic Go patterns commonly tested in technical interviews.

1. What are the key differences between Go and other languages like Java or Python?

beginner

Go is statically typed and compiled to native machine code, making it much faster than Python. Unlike Java, Go has no classes or inheritance; it uses structs and interfaces for composition, and goroutines/channels replace threads for concurrency.

2. What is a goroutine and how does it differ from a thread?

beginner

A goroutine is a lightweight, cooperatively-scheduled unit of execution managed by the Go runtime, not the OS. Goroutines start with only ~2KB of stack (which grows dynamically) and thousands can run concurrently, whereas OS threads typically use ~1MB of stack and are more expensive to create and context-switch.

3. What is the zero value in Go and why does it matter?

beginner

Every type in Go has a zero value—integers default to 0, booleans to false, strings to "", pointers/slices/maps/channels to nil. This eliminates undefined behavior from uninitialized variables and allows safe usage without explicit initialization.

4. Explain the difference between a slice and an array in Go.

beginner

An array has a fixed size defined at compile time and is a value type (copied on assignment). A slice is a dynamic, reference-type view over an underlying array with a length and capacity, and it is passed by reference semantics, making it far more commonly used.

5. What is the difference between new() and make() in Go?

beginner

new(T) allocates zeroed memory for type T and returns a *T pointer. make() is used only for slices, maps, and channels to initialize their internal data structures and returns the type itself (not a pointer), ready for use.

6. How do interfaces work in Go? What is implicit interface satisfaction?

beginner

An interface in Go defines a set of method signatures. A type satisfies an interface implicitly by implementing all its methods—there is no explicit 'implements' keyword. This promotes loose coupling and makes testing with mocks straightforward.

7. What is defer in Go and when would you use it?

beginner

defer schedules a function call to execute just before the surrounding function returns, regardless of how it returns (normally or via panic). It is commonly used for cleanup tasks like closing files, releasing locks, or logging, ensuring resources are freed even when errors occur.

8. Explain how channels work in Go and the difference between buffered and unbuffered channels.

intermediate

A channel is a typed conduit for communicating between goroutines safely. An unbuffered channel (make(chan T)) blocks the sender until a receiver is ready and vice versa, providing synchronization. A buffered channel (make(chan T, n)) allows up to n values to be queued before blocking the sender.

9. What is a race condition and how do you detect and prevent one in Go?

intermediate

A race condition occurs when two goroutines access shared memory concurrently and at least one access is a write without synchronization. Go provides the -race flag (go run -race or go test -race) to detect races at runtime, and they are prevented using sync.Mutex, sync.RWMutex, or channels.

10. Explain the select statement in Go.

intermediate

select lets a goroutine wait on multiple channel operations simultaneously, executing whichever case is ready first. If multiple cases are ready, one is chosen at random. A default case makes select non-blocking, executing immediately if no channel is ready.

11. What is the difference between value receivers and pointer receivers on methods?

intermediate

A value receiver operates on a copy of the struct, so mutations do not affect the original. A pointer receiver operates on the actual struct, allowing mutation and avoiding the cost of copying large structs. A type satisfies an interface with pointer receivers only when the variable is addressable (pointer type).

12. How does Go handle error handling, and what are best practices?

intermediate

Go treats errors as values using the built-in error interface; functions typically return (result, error) and callers check the error explicitly. Best practices include wrapping errors with fmt.Errorf("context: %w", err) for context, using errors.Is/errors.As for inspection, and avoiding panic except for truly unrecoverable situations.

13. What is a context.Context and why is it important in Go?

intermediate

context.Context carries deadlines, cancellation signals, and request-scoped values across API boundaries and goroutines. It is critical for cancelling long-running operations (HTTP requests, DB queries) gracefully, preventing goroutine leaks, and propagating timeouts in microservices.

14. Explain the sync.WaitGroup and how it is used.

intermediate

sync.WaitGroup is used to wait for a collection of goroutines to finish. You call wg.Add(n) before launching goroutines, each goroutine calls wg.Done() when it completes, and the main goroutine blocks on wg.Wait() until the counter reaches zero.

15. What is an escape analysis in Go and how does it affect performance?

advanced

Escape analysis is performed by the Go compiler to determine whether a variable should be allocated on the stack or the heap. Variables that do not escape the function scope are stack-allocated (cheap, no GC pressure); those that escape (e.g., returned pointers) are heap-allocated and managed by the garbage collector.

16. How does Go's garbage collector work and how can you minimize its impact?

advanced

Go uses a concurrent, tri-color mark-and-sweep GC that runs mostly in parallel with the application to minimize stop-the-world pauses. To minimize GC pressure, reduce heap allocations by reusing objects with sync.Pool, prefer stack-allocated values, avoid unnecessary interface boxing, and tune GOGC to control GC frequency.

17. What are functional options and why are they preferred for constructor patterns in Go?

advanced

Functional options is a pattern where a constructor accepts variadic functions (type Option func(*Config)) that modify a configuration struct, providing a flexible, extensible, and backward-compatible API without breaking changes. It avoids large parameter lists and makes optional settings explicit and self-documenting.

18. Explain how Go modules work and the purpose of go.mod and go.sum.

intermediate

Go modules (introduced in Go 1.11) are the standard dependency management system. go.mod defines the module path and its direct dependencies with versions, while go.sum contains cryptographic checksums of all dependency content to ensure reproducible and tamper-proof builds. Commands like go get, go mod tidy, and go mod vendor manage the dependency graph.

19. What is the difference between sync.Mutex and sync.RWMutex, and when would you use each?

advanced

sync.Mutex provides exclusive locking—only one goroutine can hold it at a time for either reading or writing. sync.RWMutex allows multiple concurrent readers (RLock) but only one writer (Lock) at a time. Use RWMutex when reads vastly outnumber writes to improve throughput on read-heavy workloads.

20. How would you implement a worker pool pattern in Go?

advanced

Create a fixed number of goroutines (workers) that each read from a shared jobs channel, process tasks, and optionally send results to a results channel. The main goroutine sends work into the jobs channel and closes it when done, causing workers to exit their range loops. A sync.WaitGroup ensures all workers finish before the program exits.

Practice these out loud with an AI interviewer that grills you and grades your answers.
Open the app — free to start

© RM Full Stack & AI Engineer · All interview questions · Roadmaps · Open the app