HireHireInterview Quizzes › Golang Developer

Golang Developer Interview Questions

Think you're ready? These are the questions that actually decide Golang Developer interviews. Warm up on Easy — then face the Hard round, where 95% of candidates crumble. 30 questions across 3 levels, instant score, completely free.

30Questions
3Difficulty levels
95%Fail the hard round
FreeInstant score
Easy
Warm-up · 10 Qs
Medium
Practical · 10 Qs
Hard
Brutal · 10 Qs
⚡ Take the Golang Developer quiz — get your score →

The Golang Developer interview questions

Below are the real questions, grouped by difficulty. Expand any one to reveal the correct answer and why — or take the timed quiz for a score you can share. Can you clear the Hard round?

Easy round 10 questions

What does the built-in `make` function do that `new` does not?
  • A. `make` allocates and initializes slices, maps, and channels, returning an initialized (non-zeroed) value of that type ✓
  • B. `make` allocates zeroed memory for any type and returns a pointer to it
  • C. `make` is only used for struct allocation and returns a pointer
  • D. `make` and `new` are identical and interchangeable
Correct answer: A. `make` is specifically for slices, maps, and channels and returns an initialized value, whereas `new` returns a pointer to zeroed memory for any type.
In Go, what happens if you send on a channel that no goroutine will ever receive from (an unbuffered channel with no receiver)?
  • A. The send returns immediately and the value is discarded
  • B. The sending goroutine blocks forever, causing a deadlock if all goroutines are blocked ✓
  • C. The send panics immediately
  • D. The value is buffered until a receiver appears
Correct answer: B. A send on an unbuffered channel blocks until a receiver is ready; if none ever will be, the goroutine blocks and the runtime reports a deadlock when all goroutines are blocked.
What is the correct way to signal to multiple goroutines that they should stop work in idiomatic Go?
  • A. Call `runtime.Goexit()` from the main goroutine
  • B. Set a shared boolean variable without synchronization
  • C. Use a `context.Context` and cancel it, having goroutines watch `ctx.Done()` ✓
  • D. Close the goroutines with the `kill` built-in
Correct answer: C. `context.Context` with cancellation is the idiomatic pattern; goroutines select on `ctx.Done()` to know when to stop.
Given `s := []int{1, 2, 3}` and `t := s[:2]`, what is `cap(t)`?
  • A. 2
  • B. 3 ✓
  • C. 1
  • D. 0
Correct answer: B. Slicing keeps the same underlying array, so `t` shares the original capacity of 3 from index 0 to the end of the backing array.
What does the `defer` statement guarantee about its execution order when multiple defers exist in a function?
  • A. Deferred calls run in the order they were declared (FIFO)
  • B. Deferred calls run in last-in-first-out (LIFO) order as the function returns ✓
  • C. Deferred calls run concurrently in separate goroutines
  • D. Only the last deferred call executes
Correct answer: B. Deferred function calls are pushed onto a stack and executed in LIFO order when the surrounding function returns.
In Go's error handling, what is the purpose of `errors.Is` versus a direct `==` comparison?
  • A. `errors.Is` unwraps the error chain to check if any error in the chain matches the target, handling wrapped errors ✓
  • B. `errors.Is` is only for comparing error strings
  • C. `errors.Is` converts an error to a specific concrete type
  • D. `errors.Is` is a deprecated alias for `==`
Correct answer: A. `errors.Is` walks the wrapped-error chain (via `Unwrap`) to find a match, whereas `==` only compares the top-level error value.
A struct value satisfies an interface with a method defined on a pointer receiver. Which is true?
  • A. Both the value and a pointer to it satisfy the interface
  • B. Only a pointer to the value satisfies the interface; the value itself does not ✓
  • C. Only the value satisfies the interface, not the pointer
  • D. Neither satisfies the interface
Correct answer: B. Methods with pointer receivers are in the method set of the pointer type only, so only `*T` satisfies the interface, not `T`.
What is the zero value of a `map[string]int` that has been declared but not initialized with `make` or a literal?
  • A. An empty, ready-to-use map
  • B. `nil`; reading returns zero values but writing to it panics ✓
  • C. A map with one default entry
  • D. A compile-time error
Correct answer: B. An uninitialized map is `nil`; you can read from it (getting zero values) but writing to it causes a runtime panic.
Which statement about goroutines and OS threads is correct?
  • A. Each goroutine maps one-to-one to an OS thread
  • B. Goroutines are multiplexed onto a smaller number of OS threads by the Go runtime scheduler ✓
  • C. Goroutines run only on a single OS thread regardless of GOMAXPROCS
  • D. Goroutines are OS-level processes
Correct answer: B. The Go runtime uses an M:N scheduler that multiplexes many lightweight goroutines onto a smaller pool of OS threads.
What does `sync.Mutex`'s `Lock`/`Unlock` protect against?
  • A. Deadlocks in all cases automatically
  • B. Concurrent access to shared data by ensuring only one goroutine enters the critical section at a time ✓
  • C. Goroutine leaks
  • D. Slow channel operations
Correct answer: B. A mutex provides mutual exclusion so only one goroutine can hold the lock and access the guarded critical section at once, preventing data races.

Medium round 10 questions

You call a function that returns an error, but you only care whether it failed, not the specific value. What is the idiomatic way to handle an error you intend to deliberately ignore?
  • A. Assign it to the blank identifier: `_ = doSomething()` ✓
  • B. Wrap the call in a `recover()` block
  • C. Assign it to a variable named `err` and never read it
  • D. Cast the error to `nil` explicitly
Correct answer: A. Assigning to the blank identifier `_` is the idiomatic Go way to explicitly discard a return value, including an error you intend to ignore.
In a `for range` loop over a slice, you launch a goroutine inside the loop that references the loop variable. In Go 1.22+, what is true about the loop variable's scope?
  • A. The loop variable is shared across all iterations, so all goroutines may see the final value
  • B. Each iteration gets a fresh copy of the loop variable, avoiding the classic capture bug ✓
  • C. Goroutines cannot access loop variables at all without explicit passing
  • D. The loop variable is always nil inside goroutines
Correct answer: B. As of Go 1.22, the loop variable is scoped per-iteration, so each goroutine captures its own copy rather than a shared variable.
You have a `map[string]int` and want to check whether a key exists without confusing a missing key with a stored zero value. What do you do?
  • A. Use `v := m[key]` and check if `v == 0`
  • B. Use the two-value form `v, ok := m[key]` and check `ok` ✓
  • C. Call `len(m[key])` to test presence
  • D. Use `if m[key] != nil`
Correct answer: B. The comma-ok idiom `v, ok := m[key]` returns a boolean `ok` that reliably distinguishes a present key from an absent one, even when the value is the zero value.
A function accepts an `io.Reader`. You want to pass it a string as input to read from. Which is the correct approach?
  • A. Pass the string directly since strings implement io.Reader
  • B. Use `strings.NewReader(s)` to wrap the string ✓
  • C. Convert the string with `[]byte(s)` and pass it directly
  • D. Use `bytes.NewBufferString(s).String()`
Correct answer: B. `strings.NewReader` returns a `*strings.Reader` that implements `io.Reader`, which is the standard way to adapt a string for reader-based APIs.
You want to guarantee a resource (like a file or mutex unlock) is released when a function returns, regardless of which return path is taken. What is the idiomatic mechanism?
  • A. A `defer` statement placed right after acquiring the resource ✓
  • B. A `finally` block at the end of the function
  • C. Calling the cleanup manually before every `return`
  • D. Wrapping the body in `recover()`
Correct answer: A. `defer` schedules a call to run when the surrounding function returns via any path, making it the idiomatic way to ensure cleanup like `Close()` or `Unlock()`.
Two goroutines increment a shared integer counter without synchronization. What does `go run -race` typically report, and what is the correct fix?
  • A. No issue; integer writes are atomic in Go by default
  • B. A data race; fix with a `sync.Mutex` or `sync/atomic` operations ✓
  • C. A deadlock; fix by adding more goroutines
  • D. A compile error; fix by making the counter a global variable
Correct answer: B. Concurrent unsynchronized writes to a shared variable are a data race detectable by the `-race` flag, and the standard fixes are a mutex or the `sync/atomic` package.
You want to run an operation with a timeout so it is cancelled if it exceeds 2 seconds. What is the idiomatic Go approach?
  • A. Start a `time.Sleep(2*time.Second)` in a goroutine and kill the operation
  • B. Use `context.WithTimeout` and pass the context to the operation ✓
  • C. Set a global variable `timeout = 2` and poll it
  • D. Use `runtime.Gosched()` after 2 seconds
Correct answer: B. `context.WithTimeout` creates a context that is automatically cancelled after the duration, and passing it to context-aware operations is the idiomatic cancellation pattern.
You define `type MyError struct{...}` and want it to satisfy the `error` interface. What must you do?
  • A. Add a field named `error` to the struct
  • B. Implement a method `Error() string` on the type ✓
  • C. Embed the built-in `error` type
  • D. Register the type with `errors.Register`
Correct answer: B. The `error` interface requires a single method `Error() string`, so implementing that method makes any type satisfy `error`.
You receive a wrapped error created with `fmt.Errorf("...: %w", err)` and need to check whether the underlying cause is `sql.ErrNoRows`. Which function do you use?
  • A. `errors.Is(err, sql.ErrNoRows)` ✓
  • B. `err == sql.ErrNoRows`
  • C. `reflect.DeepEqual(err, sql.ErrNoRows)`
  • D. `errors.New(err) == sql.ErrNoRows`
Correct answer: A. `errors.Is` walks the wrapped error chain (via `%w`) and reports whether any error in it matches the target, unlike a direct `==` comparison which fails on wrapped errors.
A function reads from a channel in a loop with `for v := range ch`. When does this loop terminate cleanly?
  • A. When the channel value equals nil
  • B. When the channel is closed and drained ✓
  • C. After exactly one receive
  • D. It never terminates and must be broken manually
Correct answer: B. A `for range` over a channel receives values until the channel is closed and all buffered values are drained, at which point the loop ends.

Hard round 10 questions

You have this code: ```go func do() error { var p *MyError // MyError implements error if false { p = &MyError{} } return p } func main() { if err := do(); err != nil { fmt.Println("got error") } else { fmt.Println("no error") } } ``` What prints, and why?
  • A. "no error", because a nil *MyError returned as error is a nil interface
  • B. "got error", because the returned interface holds a non-nil type descriptor even though its value pointer is nil ✓
  • C. A compile error, because you cannot return a typed nil as an error interface
  • D. A panic at runtime when err is compared to nil
Correct answer: B. An interface is a (type, value) pair; assigning a typed nil *MyError makes the type word non-nil, so the interface != nil even though the underlying pointer is nil.
In Go 1.21 (before the 1.22 loop-variable change), this runs: ```go var wg sync.WaitGroup for i := 0; i < 3; i++ { wg.Add(1) go func() { defer wg.Done(); fmt.Print(i) }() } wg.Wait() ``` What is the most accurate statement about its output?
  • A. It always prints exactly "012" in order
  • B. It reliably prints "333" every run
  • C. It commonly prints "333" but the exact digits are unspecified because all goroutines share one i whose final value is 3 and scheduling is nondeterministic ✓
  • D. It is a compile error because i is captured by reference
Correct answer: C. Pre-1.22 all closures capture the same i, which reaches 3 after the loop; output is nondeterministic but typically "333" since goroutines usually run after the loop completes.
Consider: ```go a := []int{1, 2, 3, 4} b := a[:2] b = append(b, 99) fmt.Println(a) ``` What does this print?
  • A. [1 2 3 4]
  • B. [1 2 99 4] ✓
  • C. [1 2 99]
  • D. [1 2 3 4 99]
Correct answer: B. b shares a's backing array with len 2 but cap 4, so append writes 99 into index 2 in place, overwriting the original 3.
A service shows steadily rising memory and `runtime.NumGoroutine()` climbing without bound. The suspect code: ```go func fetch(ctx context.Context) int { ch := make(chan int) go func() { ch <- expensive() }() select { case v := <-ch: return v case <-ctx.Done(): return -1 } } ``` What is the leak and the minimal fix?
  • A. No leak; the goroutine is garbage collected once fetch returns
  • B. On ctx cancellation the goroutine blocks forever on the unbuffered send; make ch buffered with capacity 1 so the send always completes ✓
  • C. The leak is expensive() being slow; wrap it in a timeout instead
  • D. Add defer close(ch) in fetch so the goroutine's send unblocks
Correct answer: B. When ctx.Done fires first, no one ever receives from the unbuffered ch, so the sender blocks permanently; a capacity-1 buffer lets the send complete and the goroutine exit.
Which statement about a nil pointer receiver is correct? ```go type T struct{ v int } func (t *T) Ping() string { return "pong" } func (t *T) Val() int { return t.v } ```
  • A. Calling Ping on a nil *T panics because the receiver is nil
  • B. Calling Ping on a nil *T works, but calling Val on a nil *T panics when it dereferences t.v ✓
  • C. Both Ping and Val panic on a nil *T
  • D. Neither panics; t.v on a nil receiver returns the zero value safely
Correct answer: B. A method call on a nil pointer receiver is legal; it only panics if the body dereferences the pointer, so Ping is safe but Val's access to t.v faults.
Two goroutines run concurrently with no synchronization: ```go m := map[int]int{} go func() { for i := 0; i < 1000; i++ { m[i] = i } }() go func() { for i := 0; i < 1000; i++ { m[i] = -i } }() ``` What is the defined behavior?
  • A. The map ends up with some interleaved mix of values but never crashes
  • B. The Go runtime may detect concurrent map writes and deliberately panic with "concurrent map writes" ✓
  • C. It always produces a data-race compile error without -race
  • D. Writes are serialized internally by a built-in map lock, so it is safe
Correct answer: B. Go maps are not concurrency-safe and the runtime intentionally detects concurrent access, calling fatal("concurrent map writes") to fail loudly rather than corrupt silently.
What does this print? ```go func f() (result int) { defer func() { result *= 2 }() result = 5 return 10 } fmt.Println(f()) ```
  • A. 10
  • B. 20 ✓
  • C. 5
  • D. 15
Correct answer: B. return 10 sets the named return value result to 10, then the deferred closure runs and doubles it to 20 before the function actually returns.
You need a cache read heavily, written rarely, with keys of one type, from many goroutines. For lowest overhead in the read-dominated steady state, which choice is best and why?
  • A. sync.Map, because it is always faster than a mutex-guarded map
  • B. A plain map guarded by sync.Mutex, because mutexes are cheaper than atomics
  • C. A plain map guarded by sync.RWMutex, allowing concurrent readers while still serializing the rare writes ✓
  • D. An unsynchronized map, since rare writes make races unlikely
Correct answer: C. An RWMutex lets many readers proceed in parallel and only blocks for the infrequent writes, which fits a read-heavy homogeneous-key workload better than the interface-boxing and dual-store overhead of sync.Map.
Regarding escape analysis, which scenario forces the value to be heap-allocated? ```go // A func a() int { x := 42; return x } // B func b() *int { x := 42; return &x } // C func c() { x := 42; _ = x } ```
  • A. A, because returning a value copies it to the heap
  • B. B, because the address of x outlives the stack frame so it must escape to the heap ✓
  • C. C, because unused locals are heap-allocated
  • D. None; Go always stack-allocates locals declared with :=
Correct answer: B. Returning &x means the pointer outlives b's frame, so escape analysis moves x to the heap; a and c keep x on the stack.
In a select with multiple ready cases: ```go select { case <-a: // ... case <-b: // ... default: // ... } ``` Which statement is precisely correct?
  • A. Cases are evaluated top-to-bottom, so a wins whenever both a and b are ready
  • B. If both a and b are ready, one is chosen uniformly at random; default runs only when neither a nor b is ready ✓
  • C. default is chosen whenever it is present, making the select non-blocking always
  • D. The select blocks until all channels are ready
Correct answer: B. When several non-default cases are ready select picks one at pseudo-random (no priority), and the default clause only executes when no other case can proceed immediately.

Prep for another role

Questions are original, written and independently verified for HireHire's role interview quizzes. They reflect the kind of knowledge Golang Developer interviews test, not any specific company's questions. HireHire maps live tech & IT jobs across India, updated regularly. Last updated: July 2026.