Top 40 Go (Golang) Interview Questions 2026 — Complete Guide with Solutions
Go developers are among the highest-paid backend engineers in India. Mid-level Go engineers earn ₹18-35 LPA at Series B+ startups; senior Go developers at Zerodha, Razorpay, and CRED command ₹40-70 LPA. The reason? Go powers the entire cloud-native stack — Docker, Kubernetes, Prometheus, Terraform, CockroachDB are all written in Go. If you know Go deeply, you're not just a developer — you're the person who understands the infrastructure.
Go backend roles grew 200%+ in India since 2023, driven by fintechs, crypto exchanges, and cloud companies. This guide covers 40 battle-tested questions compiled from real interviews at Google, Zerodha, Razorpay, Juspay, Swiggy, and CRED — from goroutine basics to scheduler internals, covering SDE-2 through Principal Engineer levels.
Related: Docker Interview Questions 2026 | Kubernetes Interview Questions 2026 | Microservices Interview Questions 2026
What Interviewers Test in Go
| Level | Topics |
|---|---|
| Junior | Syntax, goroutines basics, channels, defer, error handling |
| Mid-level | Context, select, interfaces, concurrency patterns, benchmarking |
| Senior | Memory model, scheduler internals, generics, profiling, design patterns |
BEGINNER LEVEL (Questions 1–12)
Q1. What makes Go different from other languages?
Go was designed at Google to solve systems programming at scale. Its distinguishing features:
| Feature | Go | Java/C# | Python |
|---|---|---|---|
| Compilation | Fast, native binary | Slow, bytecode | Interpreted |
| Memory | GC, no manual management | GC | GC |
| Concurrency | Goroutines + channels (CSP) | Threads | GIL-limited |
| Types | Static, structural interfaces | Static, nominal | Dynamic |
| Generics | Since 1.18 | Since Java 5 | Duck typing |
| Error handling | Explicit return values | Exceptions | Exceptions |
| Binary size | Single static binary | JVM required | Runtime required |
Go's philosophy: simplicity, readability, and fast compilation. No implicit behavior, no magic.
Q2. Explain goroutines vs OS threads.
A goroutine is a lightweight user-space thread managed by the Go runtime, not the OS.
| OS Thread | Goroutine | |
|---|---|---|
| Stack size | ~1–8 MB (fixed) | ~2 KB (growable) |
| Creation cost | Expensive (syscall) | ~1–2 µs |
| Context switch | Expensive (kernel) | Cheap (runtime) |
| Max count | Thousands | Millions |
| Communication | Shared memory + locks | Channels (CSP) |
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("Goroutine %d running\n", id)
}(i) // pass i as argument to avoid closure capture bug
}
wg.Wait()
fmt.Println("All done")
}
Common bug: capturing loop variable by reference in a goroutine without passing it as an argument.
Q3. What are channels and how do buffered channels differ from unbuffered?
Channels are typed conduits for communication between goroutines.
// Unbuffered channel — sender blocks until receiver is ready (synchronization point)
ch := make(chan int)
// Buffered channel — sender blocks only when buffer is full
bch := make(chan int, 5)
// Example: fan-out worker pool
func workerPool(jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
results <- job * job
}
}
func main() {
jobs := make(chan int, 10)
results := make(chan int, 10)
var wg sync.WaitGroup
// Start 3 workers
for w := 0; w < 3; w++ {
wg.Add(1)
go workerPool(jobs, results, &wg)
}
// Send 9 jobs
for j := 1; j <= 9; j++ {
jobs <- j
}
close(jobs)
// Close results after all workers finish
go func() {
wg.Wait()
close(results)
}()
for result := range results {
fmt.Println(result)
}
}
Q4. What is the select statement?
select lets a goroutine wait on multiple channel operations simultaneously. It picks a case at random when multiple are ready.
func processWithTimeout(work <-chan string, timeout time.Duration) {
timer := time.NewTimer(timeout)
defer timer.Stop()
for {
select {
case task, ok := <-work:
if !ok {
fmt.Println("Channel closed, done")
return
}
fmt.Println("Processing:", task)
case <-timer.C:
fmt.Println("Timeout!")
return
default:
// Non-blocking: runs if no other case is ready
fmt.Println("No work available, idling...")
time.Sleep(100 * time.Millisecond)
}
}
}
Q5. Explain defer, panic, and recover.
// defer: executes after surrounding function returns, in LIFO order
func readFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // guaranteed to run even if panic occurs
// process file...
return nil
}
// defer with named return — can modify return value
func divide(a, b float64) (result float64, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered panic: %v", r)
}
}()
if b == 0 {
panic("division by zero")
}
return a / b, nil
}
// recover must be called directly in a deferred function
func safeGo(fn func()) {
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered panic: %v\n%s", r, debug.Stack())
}
}()
fn()
}
Pitfall: defer in a loop creates deferred calls that don't execute until the function returns, not each loop iteration. Move the body to a separate function.
Q6. How does error handling work in Go?
Go uses explicit error return values. Errors are values that implement the error interface.
// Basic error handling
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// Wrapping errors (Go 1.13+)
func processOrder(id string) error {
order, err := db.GetOrder(id)
if err != nil {
return fmt.Errorf("processOrder %s: %w", id, err)
}
// ...
return nil
}
// Custom error type
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
}
// Unwrapping errors
err := processOrder("order-123")
var dbErr *DatabaseError
if errors.As(err, &dbErr) {
log.Printf("DB error code: %d", dbErr.Code)
}
if errors.Is(err, ErrNotFound) {
return http.StatusNotFound
}
Q7. What are interfaces in Go?
Go interfaces are implicit — a type implements an interface by having all the required methods (structural typing, not declared).
type Shape interface {
Area() float64
Perimeter() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 { return r.Width * r.Height }
func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) }
// Polymorphic function
func printShape(s Shape) {
fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}
// Empty interface (prefer 'any' in Go 1.18+)
func printAny(v any) {
switch t := v.(type) {
case int: fmt.Printf("int: %d\n", t)
case string: fmt.Printf("string: %s\n", t)
default: fmt.Printf("unknown: %T\n", t)
}
}
Q8. What is the context package and why is it important?
context.Context carries deadlines, cancellation signals, and request-scoped values across goroutines and API boundaries.
func fetchUserData(ctx context.Context, userID string) (*User, error) {
// Respect context deadline/cancellation
req, err := http.NewRequestWithContext(ctx, "GET", "/user/"+userID, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
if ctx.Err() != nil {
return nil, fmt.Errorf("request cancelled: %w", ctx.Err())
}
return nil, err
}
defer resp.Body.Close()
// parse response...
}
// HTTP handler with timeout
func handler(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel() // ALWAYS defer cancel to prevent context leak
user, err := fetchUserData(ctx, r.URL.Query().Get("id"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(user)
}
// Context values — use typed keys to avoid collisions
type contextKey string
const requestIDKey contextKey = "requestID"
func withRequestID(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, requestIDKey, id)
}
func getRequestID(ctx context.Context) string {
if id, ok := ctx.Value(requestIDKey).(string); ok {
return id
}
return ""
}
Q9. What is a goroutine leak and how do you prevent it?
A goroutine leak occurs when a goroutine is started but never exits — it blocks forever on a channel receive, waiting for something that never comes.
// LEAK: goroutine blocks forever if nobody sends on ch
func badSearch(query string) chan string {
ch := make(chan string)
go func() {
result := expensiveSearch(query)
ch <- result // blocks forever if caller abandons the channel
}()
return ch
}
// FIX: use context for cancellation
func goodSearch(ctx context.Context, query string) <-chan string {
ch := make(chan string, 1) // buffered so goroutine doesn't block
go func() {
result := expensiveSearch(query)
select {
case ch <- result:
case <-ctx.Done(): // caller cancelled — goroutine exits cleanly
}
}()
return ch
}
Tools: goleak (uber-go) for detecting leaks in tests.
Q10. Explain Go's memory model briefly.
Go's memory model defines when reads of variables in one goroutine are guaranteed to observe writes from another. Key guarantees:
- Within a goroutine: operations execute in the order they appear.
- Between goroutines: a send on a channel happens-before the corresponding receive completes.
- sync.Mutex:
Unlockhappens-before any subsequentLock. - Once.Do: the call to
Do(f)completes before any call returns.
Without synchronization, the Go compiler and CPU may reorder operations. Always use channels, mutexes, or sync/atomic to synchronize concurrent access.
Q11. What is the difference between new and make?
// new(T) allocates zero-value of T and returns *T
p := new(int) // *int pointing to 0
s := new([]int) // *[]int pointing to nil slice header
// make(T, ...) initializes and returns T (for slice, map, chan only)
sl := make([]int, 5, 10) // slice with len=5, cap=10
m := make(map[string]int) // initialized map (nil map panics on write)
ch := make(chan int, 2) // buffered channel
// In practice, composite literals are preferred
sl2 := []int{0, 0, 0, 0, 0}
m2 := map[string]int{}
Q12. How does Go handle nil?
Go's nil is a zero value for pointers, channels, maps, slices, interfaces, and functions. Key subtleties:
// Interface nil trap — the most common gotcha
type MyError struct{ msg string }
func (e *MyError) Error() string { return e.msg }
// BAD: returns non-nil interface with nil pointer
func getError(fail bool) error {
var err *MyError
if fail {
err = &MyError{"something failed"}
}
return err // even when err == nil pointer, this is NOT nil interface!
}
// GOOD:
func getErrorFixed(fail bool) error {
if fail {
return &MyError{"something failed"}
}
return nil // returns nil interface
}
// Nil slice is usable (not nil map which panics on write)
var s []int
s = append(s, 1, 2, 3) // works fine
len(s) // 3
var m map[string]int
m["key"] = 1 // PANIC: assignment to nil map
Confident on Q1-Q12? You've cleared the Go screening round at most companies. The intermediate section covers generics, context, concurrency patterns, and profiling — the exact topics that separate ₹15 LPA offers from ₹30 LPA+ offers.
INTERMEDIATE LEVEL — Essential Go Patterns (Questions 13–28)
Q13. Explain Go generics (Go 1.18+) with practical examples.
Go 1.18 introduced generics with type parameters. Go 1.21 added slices, maps, and cmp built-in packages built on generics.
// Generic function with type constraint
func Map[T, U any](slice []T, fn func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
func Filter[T any](slice []T, fn func(T) bool) []T {
var result []T
for _, v := range slice {
if fn(v) {
result = append(result, v)
}
}
return result
}
func Reduce[T, U any](slice []T, initial U, fn func(U, T) U) U {
result := initial
for _, v := range slice {
result = fn(result, v)
}
return result
}
// Type constraint with interface
type Number interface {
~int | ~int32 | ~int64 | ~float32 | ~float64
}
func Sum[T Number](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}
// Generic data structures
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) { s.items = append(s.items, item) }
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
last := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return last, true
}
// Usage
nums := []int{1, 2, 3, 4, 5}
doubled := Map(nums, func(n int) int { return n * 2 })
evens := Filter(nums, func(n int) bool { return n%2 == 0 })
total := Sum(nums) // 15
Q14. What are common concurrency patterns in Go?
Pattern 1: Pipeline
func generate(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
// Usage: generate → square → consume
for n := range square(generate(2, 3, 4)) {
fmt.Println(n) // 4, 9, 16
}
Pattern 2: Fan-Out, Fan-In
func merge(channels ...<-chan int) <-chan int {
var wg sync.WaitGroup
merged := make(chan int)
output := func(ch <-chan int) {
defer wg.Done()
for v := range ch {
merged <- v
}
}
wg.Add(len(channels))
for _, ch := range channels {
go output(ch)
}
go func() {
wg.Wait()
close(merged)
}()
return merged
}
Pattern 3: Done channel for cancellation
func doWork(done <-chan struct{}, stream <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for v := range stream {
select {
case out <- v * 2:
case <-done:
return
}
}
}()
return out
}
Q15. How does sync.Mutex differ from sync.RWMutex?
// Mutex: exclusive lock — one goroutine at a time
type SafeCounter struct {
mu sync.Mutex
v map[string]int
}
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
defer c.mu.Unlock()
c.v[key]++
}
// RWMutex: multiple concurrent readers OR one writer
type Cache struct {
mu sync.RWMutex
data map[string]string
}
func (c *Cache) Get(key string) (string, bool) {
c.mu.RLock() // Multiple concurrent reads allowed
defer c.mu.RUnlock()
v, ok := c.data[key]
return v, ok
}
func (c *Cache) Set(key, value string) {
c.mu.Lock() // Exclusive write lock
defer c.mu.Unlock()
c.data[key] = value
}
Use RWMutex when reads heavily outnumber writes (e.g., in-memory caches).
Q16. What is sync.Once and what is it used for?
// Thread-safe singleton initialization
type Database struct {
conn *sql.DB
}
var (
instance *Database
once sync.Once
)
func GetDB() *Database {
once.Do(func() {
conn, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
panic(err)
}
instance = &Database{conn: conn}
})
return instance
}
// sync.Once for lazy initialization
type Config struct {
once sync.Once
data map[string]string
}
func (c *Config) Load() map[string]string {
c.once.Do(func() {
c.data = loadFromFile("config.yaml")
})
return c.data
}
Q17. Explain Go's scheduler (M:N threading model).
Go uses an M:N threading model: M goroutines are multiplexed across N OS threads (where N = GOMAXPROCS, default = number of CPUs).
Components:
- G (Goroutine): the lightweight unit of work
- M (Machine): OS thread
- P (Processor): scheduling context, holds a run queue of goroutines
When a goroutine blocks on a syscall, the P detaches from M and attaches to another M, keeping the CPU busy. This is why millions of goroutines can run with only a handful of OS threads.
Work stealing: idle Ps steal goroutines from other Ps' run queues for load balancing.
Q18. How do you write benchmarks and profile Go code?
// Benchmark with testing.B
func BenchmarkSum(b *testing.B) {
nums := make([]int, 1000)
for i := range nums {
nums[i] = i
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
Sum(nums)
}
}
// Table-driven benchmarks
func BenchmarkMap(b *testing.B) {
sizes := []int{100, 1000, 10000}
for _, size := range sizes {
b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
nums := make([]int, size)
b.ResetTimer()
for i := 0; i < b.N; i++ {
Map(nums, func(n int) int { return n * 2 })
}
})
}
}
Run benchmarks: go test -bench=. -benchmem -cpuprofile cpu.prof
Profile: go tool pprof cpu.prof
Q19. What is the errgroup package?
golang.org/x/sync/errgroup runs goroutines concurrently and collects their errors.
import "golang.org/x/sync/errgroup"
func fetchAll(ctx context.Context, userID string) (*UserData, error) {
g, ctx := errgroup.WithContext(ctx)
var user *User
var orders []Order
var preferences *Preferences
g.Go(func() error {
var err error
user, err = fetchUser(ctx, userID)
return err
})
g.Go(func() error {
var err error
orders, err = fetchOrders(ctx, userID)
return err
})
g.Go(func() error {
var err error
preferences, err = fetchPreferences(ctx, userID)
return err
})
if err := g.Wait(); err != nil {
return nil, err // returns first non-nil error
}
return &UserData{User: user, Orders: orders, Prefs: preferences}, nil
}
Q20. Real-World Scenario: Rate limiter using channels
type RateLimiter struct {
tokens chan struct{}
ticker *time.Ticker
quit chan struct{}
}
func NewRateLimiter(rps int) *RateLimiter {
rl := &RateLimiter{
tokens: make(chan struct{}, rps),
ticker: time.NewTicker(time.Second / time.Duration(rps)),
quit: make(chan struct{}),
}
// Fill initial tokens
for i := 0; i < rps; i++ {
rl.tokens <- struct{}{}
}
// Refill tokens at the rate
go func() {
for {
select {
case <-rl.ticker.C:
select {
case rl.tokens <- struct{}{}: // non-blocking add
default: // bucket full, discard
}
case <-rl.quit:
return
}
}
}()
return rl
}
func (rl *RateLimiter) Allow() bool {
select {
case <-rl.tokens:
return true
default:
return false
}
}
func (rl *RateLimiter) Stop() {
rl.ticker.Stop()
close(rl.quit)
}
Q21–Q28: Quick Intermediate Round
Q21. What is the difference between a value receiver and pointer receiver? Value receiver: gets a copy, cannot modify the original. Pointer receiver: gets a pointer, can modify original. Use pointer receivers when the method needs to mutate state, the struct is large, or the type has mutex/sync fields. All methods on a type should consistently use one or the other.
Q22. What is go vet and golangci-lint?
go vet is the built-in static analyzer that catches common mistakes (wrong format verbs, unreachable code, suspicious mutex usage). golangci-lint aggregates 50+ linters including staticcheck, errcheck, gosec.
Q23. How does garbage collection work in Go?
Go uses a concurrent tri-color mark-and-sweep GC. From Go 1.17+, it aims for sub-millisecond STW (stop-the-world) pauses. Tune with GOGC (percentage of heap growth before GC triggers, default 100) and GOMEMLIMIT (soft memory ceiling, Go 1.19+).
Q24. What is an init() function?
A special function that runs after package-level variable initialization, before main(). Each package can have multiple init() functions. Use sparingly — they make order of initialization hard to reason about.
Q25. How does the range keyword work?
range iterates over arrays, slices, strings, maps, and channels. For slices: returns index, value copy. For maps: returns key, value copy (non-deterministic order). For strings: returns byte index, rune. For channels: receives values until channel is closed.
Q26. What is the stringer interface?
Any type implementing String() string satisfies fmt.Stringer. The fmt package calls this when printing the value. Generate with go generate + stringer tool for enum-like types.
Q27. What is unsafe.Pointer and when should you use it?
unsafe.Pointer bypasses Go's type system, converting between pointer types. Used for performance-critical serialization, interfacing with C via cgo, or implementing low-level data structures. Almost never needed in application code.
Q28. How do you write table-driven tests?
func TestDivide(t *testing.T) {
tests := []struct {
name string
a, b float64
want float64
wantErr bool
}{
{"normal division", 10, 2, 5, false},
{"divide by zero", 10, 0, 0, true},
{"negative", -10, 2, -5, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Divide(tt.a, tt.b)
if (err != nil) != tt.wantErr {
t.Errorf("Divide() error = %v, wantErr %v", err, tt.wantErr)
}
if !tt.wantErr && got != tt.want {
t.Errorf("Divide() = %v, want %v", got, tt.want)
}
})
}
}
If you've mastered Q1-Q28, you're already in the top 15% of Go candidates in India. The advanced section covers scheduler internals, memory model, and production system design — these questions decide ₹40 LPA+ offers at Zerodha, Google, and Cloudflare.
ADVANCED LEVEL — The No-BS Senior Round (Questions 29–40)
Q29. Implement a concurrent-safe LRU cache in Go.
import (
"container/list"
"sync"
)
type LRUCache[K comparable, V any] struct {
capacity int
mu sync.Mutex
items map[K]*list.Element
list *list.List
}
type entry[K comparable, V any] struct {
key K
value V
}
func NewLRUCache[K comparable, V any](capacity int) *LRUCache[K, V] {
return &LRUCache[K, V]{
capacity: capacity,
items: make(map[K]*list.Element),
list: list.New(),
}
}
func (c *LRUCache[K, V]) Get(key K) (V, bool) {
c.mu.Lock()
defer c.mu.Unlock()
if elem, ok := c.items[key]; ok {
c.list.MoveToFront(elem)
return elem.Value.(*entry[K, V]).value, true
}
var zero V
return zero, false
}
func (c *LRUCache[K, V]) Put(key K, value V) {
c.mu.Lock()
defer c.mu.Unlock()
if elem, ok := c.items[key]; ok {
c.list.MoveToFront(elem)
elem.Value.(*entry[K, V]).value = value
return
}
if c.list.Len() == c.capacity {
// Evict least recently used
back := c.list.Back()
if back != nil {
c.list.Remove(back)
delete(c.items, back.Value.(*entry[K, V]).key)
}
}
e := &entry[K, V]{key: key, value: value}
elem := c.list.PushFront(e)
c.items[key] = elem
}
Q30. How does Go handle circular imports and how do you break them?
Go does not allow circular imports — it's a compile error. Break circular dependencies by:
- Extracting the shared type into a third package
- Using interfaces instead of concrete types
- Restructuring the package hierarchy
// CIRCULAR: packageA imports packageB, packageB imports packageA
// FIX: Extract shared types to packageC
// packageA imports packageC
// packageB imports packageC
// packageA does NOT import packageB
Q31. What is slog (Go 1.21) and how does structured logging work?
import "log/slog"
// Default logger — outputs to stderr
slog.Info("user logged in", "userId", "u123", "ip", "192.168.1.1")
// Custom JSON logger
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
AddSource: true,
}))
// With context
ctx := context.Background()
logger.InfoContext(ctx, "order created",
slog.String("orderId", "o456"),
slog.Int("amount", 2999),
slog.Group("user",
slog.String("id", "u123"),
slog.String("email", "[email protected]"),
),
)
// Logger with fixed attributes
orderLogger := logger.With("service", "order-service", "version", "1.2.3")
Q32. How do you implement graceful shutdown in a Go HTTP server?
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
// Start server in goroutine
serverError := make(chan error, 1)
go func() {
slog.Info("Starting server", "addr", server.Addr)
if err := server.ListenAndServe(); err != http.ErrServerClosed {
serverError <- err
}
}()
// Wait for interrupt signal
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
select {
case err := <-serverError:
log.Fatalf("Server error: %v", err)
case sig := <-quit:
slog.Info("Shutting down", "signal", sig)
}
// Graceful shutdown with deadline
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("Forced shutdown: %v", err)
}
slog.Info("Server stopped gracefully")
}
Q33–Q40: Advanced Quick Fire
Q33. What is cgo and what are its tradeoffs?
cgo allows Go programs to call C code. Tradeoffs: cross-compilation becomes harder, build times increase, CGo function calls are 10–100x slower than Go calls (due to goroutine-to-OS-thread handoff), and static binary guarantee is broken.
Q34. How does Go 1.21's slices package improve performance?
slices.Sort uses pdqsort (pattern-defeating quicksort), faster than the generic sort before. Functions like slices.Contains, slices.Index reduce boilerplate. All are generic and work with any comparable/ordered type.
Q35. What is the iter package (Go 1.23)?
Introduces range-over-function iterators. Functions with signature func(yield func(V) bool) can now be used directly in for range loops, enabling lazy sequences without channels overhead.
Q36. Explain Go's escape analysis.
The compiler determines at compile time whether a variable's lifetime can be confined to the stack or must be heap-allocated. Variables that escape (returned, stored in heap-allocated struct, passed to interface) go to the heap and incur GC pressure. Check with go build -gcflags='-m' ./....
Q37. What is sync.Pool and when to use it?
sync.Pool caches temporary objects to reduce GC pressure in hot paths. Objects may be evicted at any GC cycle. Typical use: bytes.Buffer, HTTP request/response objects, serialization buffers. Not for persistent state.
Q38. How do you implement the options pattern for constructors?
type Server struct { port int; timeout time.Duration; maxConn int }
type Option func(*Server)
func WithPort(p int) Option { return func(s *Server) { s.port = p } }
func NewServer(opts ...Option) *Server {
s := &Server{port: 8080, timeout: 30*time.Second, maxConn: 100}
for _, opt := range opts { opt(s) }
return s
}
Q39. What are type assertions vs type switches?
Type assertion: v, ok := i.(ConcreteType) — extracts the concrete value from an interface. Type switch: switch v := i.(type) { case T1: ... } — branches on the dynamic type of an interface. Type switches are idiomatic for handling multiple possible types.
Q40. Real-World: How would you build a task queue with workers in Go?
Channel-based: buffered channel as the queue, goroutine pool as workers, sync.WaitGroup to drain, context for cancellation. For persistence, use Redis (with go-redis) or a DB-backed queue. For distributed systems, use message queues (NATS, Kafka) with consumer groups.
Common Go Mistakes — Avoid These and Impress Every Interviewer
- Goroutine leak — goroutine blocked on channel with no sender/receiver, no cancellation
- Nil map write —
var m map[string]int; m["key"] = 1panics; alwaysmake(map[...]...) - Defer in loop — defers accumulate until function returns, not loop iteration
- Closing a closed channel — panics; only the sender should close
- Not using
context.WithCanceland always calling cancel — context leak - Interface nil trap — returning a typed nil as
errorgives non-nil interface - Loop variable capture —
go func() { use(i) }()captures reference, not value - Using
time.Sleepfor synchronization — use channels or sync primitives
FAQ — Your Go Career Questions, Answered
Q: Is Go good for web development?
Yes. The standard library's net/http is production-grade. Frameworks like Gin, Echo, Chi, and Fiber add routing and middleware. For full-stack, Templ (Go HTML templates) + HTMX is gaining traction.
Q: Go vs Rust — which should I learn for backend roles in India? Go has far more job openings in India (Zerodha, Juspay, Razorpay, Swiggy, CRED use Go). Rust is excellent for systems programming but fewer application backend roles. Go first, Rust if you want systems/infra roles.
Q: How important are goroutine leaks in interviews?
Very important at senior level. Be able to identify leaks in code snippets and describe the fix. goleak in tests is a strong answer.
Q: What Go projects should I build for a portfolio? CLI tools, REST APIs with PostgreSQL, gRPC services, a simple key-value store, or a load balancer. Demonstrating real concurrency (not just a hello-world goroutine) matters.
Q: Is Go's error handling verbose?
Yes, and it's intentional. Companies like it because errors are visible and explicit. In interviews, show you understand idiomatic error wrapping with %w and errors.As/errors.Is.
Q: What is the recommended project layout for Go?
cmd/ for main packages, internal/ for private packages, pkg/ for public libraries (optional), api/ for protobuf/OpenAPI specs. See github.com/golang-standards/project-layout.
Q: How do you pass Go interview coding rounds?
Practice LeetCode in Go — especially graph problems (BFS/DFS using goroutines is a bonus point), string manipulation, and sliding window. Know the difference between recursive and iterative approaches. Use sort.Slice and the new slices package fluently.
Q: What is Go's approach to dependency management?
Go modules (go.mod/go.sum) since Go 1.11. Use go get, go mod tidy, go mod vendor. The Go proxy (proxy.golang.org) caches modules for reproducibility.
Practice: Implement a concurrent URL crawler, rate limiter, and in-memory key-value store with TTL. These cover goroutines, channels, mutexes, and context — the core of Go interviews.
Go is the language of infrastructure. Master it and you'll never run out of high-paying opportunities. Bookmark this, build a concurrent project, and walk into your next interview with confidence.
Related Articles:
- Docker Interview Questions 2026 — Docker is written in Go
- Kubernetes Interview Questions 2026 — K8s is written in Go
- Microservices Interview Questions 2026 — Go is the #1 choice for microservices at Indian unicorns
- TypeScript Interview Questions 2026 — pair Go backend with TypeScript frontend
- React Interview Questions 2026 — the frontend consuming your Go APIs
Explore this topic cluster
More resources in Interview Questions
Use the category hub to browse similar questions, exam patterns, salary guides, and preparation resources related to this topic.
Company hub
Explore all Top 40 Go (Golang) resources
Open the Top 40 Go (Golang) hub to jump between placement papers, interview questions, salary guides, and other related pages in one place.
Open Top 40 Go (Golang) hubRelated Articles
Top 35 Blockchain & Web3 Interview Questions 2026 — Complete Guide with Solutions
Web3 engineers are among the highest-paid developers globally. Mid-level Solidity devs earn ₹20-40 LPA in India; senior...
Top 40 Cybersecurity Interview Questions 2026 — Complete Guide with Solutions
Cybersecurity is the highest-demand, highest-paying field in tech right now. AppSec Engineers pull ₹15-35 LPA, Cloud...
Top 40 TypeScript Interview Questions 2026 — Complete Guide with Solutions
TypeScript isn't optional anymore — it's the price of admission. With 60%+ adoption among JavaScript developers, companies...
Top 50 React Interview Questions 2026 — Complete Guide with Solutions
React developers are the most in-demand frontend engineers in India. Mid-level React roles pay ₹15-30 LPA at product...
AI/ML Interview Questions 2026 — Top 50 Questions with Answers
AI/ML engineer is the highest-paid engineering role in 2026, with median compensation exceeding $200K at top companies. But...