Skip to main content

Golang engineer role-specific interview questions

28 questions on role-specific for golang engineer candidates. Expect prompts such as “How does context cancellation propagate through a call tree?” and “How should you handle and wrap errors in Go 1.13 and later?”, each with a worked answer outline and the follow-ups interviewers push on.

Questions

Showing 1 to 25 of 28 role-specific questions (page 1 of 2).

As asked

Explain how context.WithCancel works internally. If I cancel a parent context, how does the signal propagate to child contexts, and what responsibility do goroutines have when they receive a cancellation?

Sample answer outline

A strong answer explains that WithCancel creates a child cancelCtx and registers it with the parent by walking up the tree to find the nearest cancelCtx ancestor and appending to its children map. When the parent is cancelled it iterates its children and calls their cancel functions. Goroutines are responsible for selecting on ctx.Done() and returning promptly when the channel is closed, releasing resources and avoiding leaks.

Expect these follow-ups

  • What is the difference between context.WithTimeout and context.WithDeadline?
  • What happens if you forget to call the cancel function returned by WithCancel?
contextcancellationgoroutinesleaksstdlib

As asked

Describe the fmt.Errorf with %w, errors.Is, and errors.As APIs introduced in Go 1.13. When would you use each, and what is the difference between wrapping and substituting an error?

Sample answer outline

A strong answer explains that %w wraps an error so that errors.Is can traverse the chain for sentinel comparison and errors.As can extract a concrete type from anywhere in the chain. Wrapping preserves the original error for callers who care about the cause; substituting loses it. The candidate should mention when to define sentinel errors versus error types, and note that errors.Is uses == by default but types can implement Is() for custom logic.

Expect these follow-ups

  • How do you test that a function returns a specific wrapped error type?
  • What is the problem with using panic for error propagation in a Go service?
error-handlingerrorsstdlibwrappingsentinel-errors

As asked

Describe how goroutine leaks occur in Go services. What tools would you use to detect them, and walk me through fixing a concrete example where a goroutine blocks on a channel that is never closed.

Sample answer outline

A strong answer identifies the most common causes: goroutines blocked on channels with no sender, blocked on a nil channel, or waiting for a response that never comes. Detection tools include runtime.NumGoroutine in metrics, pprof goroutine profiles (go tool pprof /debug/pprof/goroutine), and the goleak testing library. The fix usually involves passing a context or done channel so goroutines have an exit path, and ensuring the caller always closes the channel or cancels the context.

Expect these follow-ups

  • How would you expose goroutine count as a Prometheus metric?
  • What does the pprof goroutine endpoint show, and how do you read the stack traces?
goroutinesleakspprofdebuggingproduction

As asked

Describe what a slice header contains, what happens internally when you call append and the underlying array is full, and give me a concrete example of a subtle bug caused by multiple slices sharing the same backing array.

Sample answer outline

A strong answer explains that a slice header is three words: a pointer to the backing array, a length, and a capacity. When append exceeds capacity, a new array is allocated (roughly doubling for small slices), data is copied, and a new slice header is returned. The bug: slicing s2 := s1[0:3] creates a slice sharing the same array. Appending within capacity to s2 silently overwrites s1's elements. The fix is s2 := s1[0:3:3] to limit capacity, or a copy.

Expect these follow-ups

  • How does the runtime decide the new capacity when growing a slice?
  • What is the difference between make([]int, 5) and make([]int, 0, 5)?
slicesmemoryappendbacking-arraybugs

As asked

Walk me through how you would structure a production-ready HTTP server in Go using net/http. How do you handle timeouts, middleware chaining, graceful shutdown, and avoiding common pitfalls like connection leaks?

Sample answer outline

A strong answer covers setting ReadHeaderTimeout, ReadTimeout, WriteTimeout, and IdleTimeout on http.Server to prevent slow-client attacks. Middleware is composed by wrapping http.Handler or using a library like chi or gorilla/mux. Graceful shutdown calls server.Shutdown with a context so in-flight requests drain. Common pitfalls include not reading the response body to completion before closing (leaving connections in the pool unusable) and not returning after writing an error response.

Expect these follow-ups

  • How does http.ServeMux differ from a third-party router like chi, and when would you pick one?
  • What happens if you write to the http.ResponseWriter after the handler returns?
httpnet-httpservertimeoutsgraceful-shutdown

As asked

Describe how you structure table-driven tests using t.Run for subtests. How do you run a single failing case without running all tests, how do you parallelize subtests, and what is the loop variable closure bug in older Go versions?

Sample answer outline

A strong answer shows defining a slice of struct cases with input, expected, and name fields, then ranging over them and calling t.Run(tc.name, func(t *testing.T) {...}). Running a single subtest uses go test -run TestFoo/case_name. To parallelize, call t.Parallel() inside the subtest. The closure bug: in Go before 1.22 the loop variable tc is shared, so capturing it inside the goroutine closure reads the last value. The fix is tc := tc before the t.Run call.

Expect these follow-ups

  • How do you write a benchmark that accurately measures a function with side effects?
  • What is testify and when would you prefer it over the standard testing package?
testingtable-drivensubtestsclosuresgo-testing

As asked

Walk me through how the Go runtime scheduler works. What is the M:N threading model, what are M, P, and G, and how does the scheduler decide which goroutine to run next?

Sample answer outline

A strong answer explains that Go uses an M:N scheduler where M machine threads are multiplexed over N goroutines via P (processor) structs that each hold a local run queue. The candidate should describe work-stealing where idle Ps steal from busy Ps, and explain that goroutines are parked and unparked on blocking syscalls via handoff. A complete answer also mentions that Go 1.14 added asynchronous preemption via OS signals (SIGURG on Unix), so goroutines can be preempted at any point and not only at function call sites. Bonus for mentioning GOMAXPROCS and the global run queue.

Expect these follow-ups

  • What happens to the OS thread when a goroutine makes a blocking syscall?
  • How does GOMAXPROCS affect throughput versus latency in a CPU-bound service?
schedulergoroutinesruntimeconcurrencym-n-threading

As asked

Describe the internal representation of an interface value in Go. What are the two words stored, and what happens when you assign a concrete type to an interface? How does a nil interface differ from an interface holding a nil pointer?

Sample answer outline

A strong answer explains that an interface value is a two-word pair: a pointer to type information (itab or typeinfo) and a data pointer. When you assign a concrete type the runtime fills in the type pointer to the vtable for that type and stores the value or a pointer to it. A nil interface has both words zero. An interface holding a nil concrete pointer has a non-nil type pointer, so the interface itself is not nil, which is a common source of bugs.

Expect these follow-ups

  • Show me a real bug caused by the nil interface versus nil pointer distinction.
  • How would you write a compile-time check that a type implements an interface?
interfacestype-systemruntimenilitab

As asked

Walk me through how Go's concurrent garbage collector works. What are the three colors, what is the write barrier for, and how does Go minimize stop-the-world pauses?

Sample answer outline

A strong answer covers tricolor marking: white objects are not yet visited, grey are reachable but children not yet scanned, black are fully scanned. The write barrier intercepts pointer writes during the concurrent mark phase to maintain the invariant that no black object points to a white object, preventing premature collection. Go's collector runs concurrently with the program, using short STW pauses only to start and stop the mark phase, plus sweep is done concurrently.

Expect these follow-ups

  • What is GOGC and how does tuning it affect heap size versus CPU overhead?
  • How would you diagnose a service that shows high GC pause times in production?
garbage-collectionruntimememorytricolorstop-the-world

As asked

If multiple cases in a select statement are ready at the same time, which one runs? How do you implement a non-blocking channel operation, and what is the idiomatic way to drain a channel with a timeout?

Sample answer outline

A strong answer states that when multiple cases are ready, Go picks one at random to avoid starvation. A non-blocking operation uses a default case. The idiomatic timeout pattern uses select with the work channel and a time.After or context Done channel. The candidate should note that before Go 1.23, time.After created a timer that was not garbage collected until it fired, making it wasteful inside a loop; since Go 1.23 the timer is eligible for collection once the channel is no longer referenced. Using context.WithTimeout is still the preferred approach in long-lived loops because it allows cancellation and avoids the pre-1.23 leak.

Expect these follow-ups

  • What is a goroutine leak and how does it relate to channels without a done signal?
  • How would you implement a rate limiter using time.Tick and a channel?
selectchannelsconcurrencytimeoutstdlib

As asked

Explain the difference between sync.Mutex and sync.RWMutex. Under what read-to-write ratio does RWMutex actually outperform Mutex, and what are the starvation risks?

Sample answer outline

A strong answer explains that RWMutex allows multiple concurrent readers but exclusive writers. It wins when reads dominate because they do not block each other. The candidate should note that if writes are frequent, the additional overhead of RWMutex (it uses a Mutex internally plus extra bookkeeping) makes it slower. Go's RWMutex has writer-preference semantics: a pending writer blocks new readers, preventing writer starvation at the cost of possibly blocking readers behind a slow writer.

Expect these follow-ups

  • Describe a scenario where you would use sync.Map instead of a map with a mutex.
  • How does the sync package's Mutex change its behavior after detecting a race?
syncmutexconcurrencyrwmutexperformance

As asked

Explain the execution order of deferred functions during a panic. In what scenarios is recover useful, and what are the pitfalls of using panic/recover as a control flow mechanism in a library?

Sample answer outline

A strong answer explains that deferred functions run in LIFO order when the surrounding function returns, including during a panic. Recover stops the panic only when called directly inside a deferred function; calling it elsewhere returns nil. The legitimate use cases are converting panics to errors at package boundaries (e.g., an HTTP handler to avoid crashing the server) and resource cleanup. Using panic for normal control flow breaks the convention that panics represent programmer errors, making the code hard to reason about.

Expect these follow-ups

  • What is the value of a named return variable inside a deferred function?
  • Does defer add meaningful overhead in a hot path, and how do you measure it?
deferpanicrecovererror-handlingruntime

As asked

Describe the pprof profiles available in net/http/pprof, how you would collect and analyze a CPU profile from a live service, and how you would identify a goroutine that is consuming unexpected CPU.

Sample answer outline

A strong answer covers the main profiles: cpu, heap, goroutine, allocs, mutex, block. To collect a CPU profile: curl the /debug/pprof/profile endpoint for 30 seconds, then run go tool pprof on the file. The flame graph or top view shows cumulative time per function. For unexpected CPU, the candidate should look at the goroutine profile for count and at the block profile if the issue is lock contention. In production, import _ net/http/pprof behind an auth middleware to avoid exposing it publicly.

Expect these follow-ups

  • What is the difference between the heap and allocs profiles?
  • How do you use go tool trace to understand goroutine scheduling latency?
pprofprofilingperformancedebuggingproduction

As asked

Explain how type parameters and constraints work in Go 1.18+. What is the comparable constraint, what can you express with interface constraints that you could not before, and what are the current limitations of Go generics compared to other languages?

Sample answer outline

A strong answer explains that type parameters are declared in brackets and constrained by interface types that may include method sets and type element unions. comparable allows == and != operations. Limitations include no method-level type parameters (only function-level or type-level), no operator overloading beyond what constraints allow, and the lack of specialization means generic code can be slower than hand-written versions. The candidate should give a concrete use case like a typed Set or a Map function.

Expect these follow-ups

  • Why can you not use fmt.Stringer as a type constraint directly in some situations?
  • How does the runtime handle generic instantiation, and does it produce separate code per type?
genericstype-parametersconstraintsgo-1.18type-system

As asked

Explain how database/sql's connection pool works, what SetMaxOpenConns, SetMaxIdleConns, and SetConnMaxLifetime control, and what symptoms you would see in a service that has these misconfigured.

Sample answer outline

A strong answer explains that database/sql maintains a pool of *sql.Conn objects. MaxOpenConns caps total connections; if all are in use, new queries wait. MaxIdleConns controls how many idle connections are kept; too low means frequent reconnects, too high wastes DB server connections. ConnMaxLifetime closes stale connections to work around DB-side firewalls or load balancer timeouts that silently drop long-lived connections. Symptoms of misconfiguration: query latency spikes, connection refused errors, or DB max_connections exhaustion.

Expect these follow-ups

  • How do you use sql.Tx correctly to avoid holding a connection longer than necessary?
  • What is the difference between QueryRow and Query, and how do you avoid a common row scan bug?
database-sqlconnection-poolpostgresperformancestdlib

As asked

Explain how struct embedding works in Go and how it differs from both inheritance and composition in object-oriented languages. What is promoted, what is not, and where can embedding lead to surprising method resolution?

Sample answer outline

A strong answer explains that embedding a type T in a struct S promotes T's exported methods and fields to S's method set, allowing S to satisfy interfaces that T satisfies. Unlike inheritance, S does not become a subtype of T. Surprising cases include method shadowing if S defines a method with the same name as T, and the receiver of the promoted method is still T, so modifications to S fields inside T's method may not be visible. Embedding interfaces in structs is a common pattern for partial implementation or mocking.

Expect these follow-ups

  • How do you embed an interface in a struct and why would you do it?
  • What happens when two embedded types both have a method with the same name?
embeddingcompositioninterfacestype-systemmethods

As asked

Walk me through implementing a bidirectional streaming gRPC service in Go. How do you manage the goroutines for sending and receiving concurrently, how do you handle context cancellation, and what error codes should you return for different failure modes?

Sample answer outline

A strong answer describes launching one goroutine for the receive loop and one or a select loop for sending, coordinating via a channel or sync mechanism. The handler should return when the stream context is done. Proper error codes matter: codes.Canceled for cancelled context, codes.DeadlineExceeded for timeout, codes.Internal for unexpected server errors. The candidate should mention stream.Context() and checking err == io.EOF on Recv to detect clean client close.

Expect these follow-ups

  • How do you add server-side interceptors for authentication and logging in gRPC?
  • What is the difference between a unary interceptor and a streaming interceptor in the grpc-go package?
grpcstreaminggoroutinescontextrpc

As asked

Using a library like go-redis, explain the difference between pipelining and transactions (MULTI/EXEC). When would you choose each, how do you handle partial failures, and how do you use Lua scripts for atomic operations?

Sample answer outline

A strong answer explains that pipelining batches multiple commands to reduce round-trip latency but does not guarantee atomicity. MULTI/EXEC executes a block atomically from Redis's perspective but still requires two round trips. Lua scripts run atomically in a single round trip and are preferred for read-modify-write operations. Partial failures in a pipeline are per-command errors in the result slice. In go-redis, client.TxPipelined or client.Watch with optimistic locking handles the transaction pattern.

Expect these follow-ups

  • How do you implement a distributed lock in Redis from Go, and what can go wrong with TTL-based locks?
  • How does go-redis handle connection pool exhaustion under load?
redisgo-redispipeliningtransactionsatomicity

As asked

Using a library like sarama or confluent-kafka-go, describe how you would build a consumer group member that processes messages reliably, commits offsets correctly, handles rebalances, and avoids duplicate processing.

Sample answer outline

A strong answer describes implementing sarama.ConsumerGroupHandler with Setup, Cleanup, and ConsumeClaim. Offsets should be committed after successful processing (not before), using manual commit to avoid losing messages. Rebalances trigger Cleanup on revoked partitions and Setup on new ones; in-flight work must be flushed or abandoned. For exactly-once semantics, the candidate should mention transactional producers or idempotent consumers using a deduplication key stored in an external store like Redis or Postgres.

Expect these follow-ups

  • What happens to uncommitted offsets when a consumer crashes during processing?
  • How does consumer group lag monitoring work, and what metric would you alert on?
kafkaconsumer-groupsaramaoffsetsreliability

As asked

Walk me through adding Prometheus instrumentation to a Go HTTP service. What metric types would you use for request latency, error rate, and queue depth, and how do you avoid high-cardinality label explosions?

Sample answer outline

A strong answer covers using prometheus/client_golang: a Histogram for request latency with predefined buckets, a Counter for total requests and errors, and a Gauge for queue depth. Labels like method and status_code are useful; label values with high cardinality (user_id, request_id) cause metric explosion and must be avoided. The candidate should mention wrapping the default ServeMux with promhttp.Handler and using MustRegister in init or a constructor.

Expect these follow-ups

  • What is the difference between a Summary and a Histogram for latency metrics?
  • How do you write a unit test for a function that increments a Prometheus counter?
prometheusmetricsobservabilityhttpinstrumentation

As asked

Walk me through setting up a production TLS configuration for a Go HTTP server. What cipher suites and TLS versions would you allow, how do you load certificates, how do you handle certificate rotation without a restart, and what common misconfigurations leave a service vulnerable?

Sample answer outline

A strong answer covers constructing a tls.Config with MinVersion set to tls.VersionTLS12 or TLS13, preferring server cipher suite ordering, and loading certs with tls.LoadX509KeyPair. Certificate rotation without restart is achieved by providing a GetCertificate callback that reads the current cert from disk or a secrets manager, called per-handshake. Common misconfigurations include InsecureSkipVerify in client configs, allowing TLS 1.0/1.1, and not setting timeouts on TLS handshakes.

Expect these follow-ups

  • How would you implement mutual TLS (mTLS) between two Go services?
  • What is certificate pinning and when is it appropriate in a Go client?
tlssecurityhttpscertificatesnet-http

As asked

Explain how testing.B benchmarks work in Go. What does b.N represent, how do you prevent the compiler from optimizing away the result, what is b.ResetTimer for, and how do you compare two implementations reliably?

Sample answer outline

A strong answer explains that the testing framework calls the benchmark function repeatedly, adjusting b.N until the total elapsed time is stable. Results must be assigned to a package-level sink variable to prevent dead-code elimination. b.ResetTimer is used after expensive setup that should not count toward the benchmark time. Comparing two implementations uses benchstat to account for variance across multiple runs; a single run is rarely sufficient to conclude significance.

Expect these follow-ups

  • What is AllocsPerOp and how do you use it to verify a zero-allocation code path?
  • How do you benchmark concurrent code where multiple goroutines share state?
benchmarkingtestingperformancetesting-bcompiler

As asked

Describe the functional options pattern for configuring Go structs. What problem does it solve compared to a config struct or variadic args, and what are its tradeoffs in terms of readability, backward compatibility, and testing?

Sample answer outline

A strong answer explains that functional options define type Option func(*Config) and accept ...Option in the constructor, applying each option to a defaults struct. This is backward-compatible because new options can be added without changing the function signature. It is more discoverable than a map of strings. Tradeoffs: it makes testing slightly more verbose because each option must be constructed, and the indirection can obscure what a constructor does. Config structs are simpler but require a version bump when adding fields.

Expect these follow-ups

  • How would you validate that required options are provided using this pattern?
  • Compare functional options to builder pattern and when you would choose each.
functional-optionsapi-designpatternsgo-idiomsconstructors

As asked

What is the hchan struct inside the Go runtime, and how do buffered and unbuffered channels differ in their send and receive paths? Where does blocking happen and how are goroutines notified?

Sample answer outline

A strong answer covers the circular buffer inside hchan for buffered channels, the sendq and recvq wait queues of goroutines blocked on the channel, and the mutex protecting the struct. The candidate should explain that on an unbuffered send, the sender parks itself on sendq until a receiver arrives, and the runtime directly copies data between goroutine stacks when a match is made. Mention gopark and goready for the blocking/unblocking mechanism.

Expect these follow-ups

  • Why can sending on a nil channel block forever but closing a nil channel panics?
  • What are the performance implications of using a channel versus a mutex for a simple counter?
channelsruntimeconcurrencyhchanblocking

As asked

Two goroutines access a shared variable without synchronization. Is this a data race in Go? How does the Go memory model define happens-before, and what synchronization primitives establish it?

Sample answer outline

A strong answer explains that concurrent reads and writes to a shared variable without synchronization is a data race and the behavior is undefined per the Go memory model. Happens-before is established by channel sends and receives (send happens before the matching receive completes), sync.Mutex Lock/Unlock pairs, sync.WaitGroup Done before Wait returns, and sync.Once. The candidate should mention the race detector (-race flag) as the practical tool for catching violations.

Expect these follow-ups

  • How does the race detector instrument code, and what is its overhead in production?
  • Can you use an atomic store and load as a synchronization point to replace a mutex?
memory-modeldata-racehappens-beforesyncrace-detector