Concurrency
Go's headline feature: goroutines and channels, plus the sync and context packages that keep them coordinated and stoppable. Interviewers push hard here because a Go hire is expected to write a bounded, cancellable, race-free worker without reaching for a framework.
part ofGo (Golang)overview, primer and where to startread it →on this pageshowhide
explore
- Goroutine Lifecycle17 questions
- The go Statement5 questions
- Goroutine Leaks4 questions
- Panics, recover and Goroutine Isolation4 questions
- Ownership and Cancellation4 questions
- Channels and select29 questions
- Unbuffered vs Buffered Channels4 questions
- Closing Channels and range4 questions
- Nil Channels and Directional Types4 questions
- select and Multiplexing4 questions
- default and Non-Blocking Operations4 questions
- Timeouts, Timers and Tickers4 questions
- Sharing by Communicating5 questions
- The sync and sync/atomic Packages35 questions
- sync.Mutex4 questions
- sync.Pool and Buffer Reuse4 questions
- sync.Map4 questions
- sync.RWMutex4 questions
- sync.WaitGroup4 questions
- sync.Once and Lazy Initialization4 questions
- sync/atomic and Atomic Types4 questions
- sync.Cond3 questions
- Lock Granularity and Sharding4 questions
- The context Package20 questions
- Context Propagation4 questions
- Cancellation with WithCancel4 questions
- Deadlines and Timeouts4 questions
- Context Values4 questions
- Cancellation Reach and Cleanup4 questions
- Coordination Patterns35 questions
- Rate Limiting and Coalescing5 questions
- Worker Pools and Bounded Concurrency4 questions
- First Result Wins4 questions
- Pipelines and Stages4 questions
- Merging Channels4 questions
- errgroup and Structured Groups5 questions
- Semaphores and Permits4 questions
- Backpressure and Load Shedding5 questions
- Races, Visibility and Detection20 questions
- The Go Memory Model and happens-before4 questions
- Data Races in Go4 questions
- The Race Detector4 questions
- Testing Concurrent Go Code4 questions
- Deadlock Shapes4 questions
- Daemons and Background Work16 questions
- Shutdown Ordering4 questions
- Supervising Long-Lived Workers4 questions
- Periodic Ticker Loops4 questions
- Draining In-Flight Tasks4 questions
questions
172 · 7 sectionsIn Go, what does the go keyword do when placed in front of a function call?
basics
~20 sIt runs that call in a new goroutine and returns immediately, so the caller carries on without waiting. It is a statement, not an expression: it produces no value, no handle and no id you can hold on to.
What is a goroutine leak in Go, and why does the runtime never reclaim a goroutine that is blocked forever?
basics
~20 sA goroutine leak is a goroutine that never returns because it is blocked forever on an operation nobody will ever complete. The runtime cannot reclaim it, so its stack and everything it references stay alive until the process exits.
How do you stop a goroutine you started in Go, given the runtime offers no kill call?
basics
~20 sYou cannot stop it from the outside. Pass the goroutine a stop signal it watches itself, either a context.Context or a done channel of type chan struct{}, and write the goroutine so it returns when that signal fires.
What happens to a Go program when a goroutine panics and nothing recovers it?
basics
~20 sThe whole process dies, not just that goroutine. The runtime prints the panic value and a stack trace and exits with status 2. Other goroutines are killed where they stand and their deferred functions never run.
In Go, when are the function value and arguments of go f(x) evaluated?
basics
~20 sImmediately, in the calling goroutine, at the go statement itself; only the call runs in the new goroutine. So go f(x) snapshots x on the spot, while go func(){ f(x) }() reads x later, whenever the goroutine happens to run.
In Go, when does a send block on make(chan int) versus make(chan int, 3)?
basics
~20 sA send on a channel made with make(chan int) blocks until another goroutine is ready to receive. With make(chan int, 3) the send returns immediately while the buffer holds fewer than three values, and blocks only once it is full.
When you send a struct value on a Go channel, does the receiver get a copy or the sender's original?
basics
~20 sA channel send copies the value. The receiver gets its own copy of the struct, so later writes by the sender are invisible. The copy is shallow: pointer, slice and map fields still refer to the same underlying data.
What does close(ch) do to a Go channel, and what do receivers see afterwards?
basics
~20 sclose(ch) marks a Go channel as finished. Receives stop blocking: they deliver any values still buffered, then return the element type's zero value with ok false, and a for range loop over the channel exits.
What does a nil channel in Go do to a goroutine that sends or receives on it?
basics
~10 sBoth operations block forever. A send on a nil channel and a receive from a nil channel park the goroutine permanently, because no counterparty can ever exist. Closing a nil channel panics instead.
What does a `default` case do in a Go `select` statement?
basics
~20 sA default case makes the select non-blocking. If no other case can proceed at that instant, Go runs default immediately instead of waiting. Without a default, the select parks the goroutine until one of its cases is ready.
What does Go's atomic.Int64 give a shared counter that a plain hits++ from many goroutines does not?
basics
~20 satomic.Int64.Add performs the read, the increment and the write as one indivisible step, so no update is lost. A plain hits++ is three separate steps and is a data race: the -race detector reports it and the total comes out too low.
How do you create a sync.Cond in Go, and what does its Wait method do to c.L?
basics
~20 ssync.NewCond(l) builds a *sync.Cond over any sync.Locker, stored in its L field. Wait must be called with c.L already held: it atomically unlocks c.L and parks the goroutine, then re-locks c.L before returning to the caller.
Why does `defer mu.Unlock()` at the top of a Go method hold the mutex longer than the data needs?
basics
~20 sA deferred unlock runs when the whole function returns, not when a block ends, so encoding, file or network work written after it still holds the mutex. Narrow it with an inner function or an early unlock.
What is Go's sync.Map, and why can't several goroutines share a plain map instead?
basics
~20 ssync.Map is a map in Go's sync package whose methods — Load, Store, LoadOrStore, Range — are safe to call from many goroutines with no lock of your own. A plain Go map is not: concurrent access with a writer is a data race.
Why does a sync.Mutex need no constructor, and what is the standard Lock and Unlock pattern?
basics
~20 sA sync.Mutex zero value is already an unlocked, usable lock, so a plain declaration needs no constructor. Idiomatic use is mu.Lock() followed immediately by defer mu.Unlock(), which releases it on every return path, panics included.
What is context.Context in Go, and why is it passed as a function's first parameter?
basics
~20 scontext.Context carries a cancellation signal, an optional deadline and request-scoped values across API boundaries. Go has no goroutine-local storage, so it is threaded explicitly through every call, first in the parameter list and named ctx.
What does context.WithCancel return, and why must the cancel function always be called?
basics
~20 scontext.WithCancel(parent) returns a derived Context plus a cancel function. Calling cancel closes that context's Done channel and detaches it from its parent. Skipping the call leaves the child attached to a long-lived parent, so its memory is retained.
In Go, what is the difference between context.WithTimeout and context.WithDeadline?
basics
~10 scontext.WithTimeout takes a duration and expires that far from now; context.WithDeadline takes an absolute time.Time and expires at that instant. WithTimeout is defined as WithDeadline with time.Now() plus the duration.
Which Go standard-library calls actually stop early when the context.Context you passed them is cancelled?
basics
~10 sOnly the calls you handed the context to. database/sql's QueryContext, http.NewRequestWithContext, net.Dialer.DialContext and exec.CommandContext abort. Anything with no ctx parameter, such as os.File.Read, io.Copy or time.Sleep, runs to completion.
Why should a context.WithValue key be an unexported custom type rather than a plain string?
basics
~20 sContext values share one namespace across every package in a program. A key of an unexported type cannot be constructed or matched by any other package, so two libraries that both use the string "userID" cannot overwrite each other's value.
What does golang.org/x/sync/errgroup add over a sync.WaitGroup for concurrent tasks that can fail?
basics
~20 serrgroup joins goroutines like sync.WaitGroup, but each task is a func() error. Group.Wait blocks until every task has returned and gives back the first non-nil error, and errgroup.WithContext also cancels the siblings when one task fails.
How do you merge several receive-only Go channels into one channel a consumer can range over?
basics
~20 sStart one forwarding goroutine per source that copies every value into a single shared output channel, and start one extra goroutine that waits for all forwarders to finish and then closes that output exactly once.
In a Go pipeline, why does each stage return a receive-only `<-chan Out` and close only that channel?
basics
~20 sA stage owns the one channel it creates and sends on, so only it may close that channel, in a defer when its input runs dry. Returning that channel receive-only makes any downstream close or send a compile error.
In golang.org/x/time/rate, what is the difference between Limiter.Allow and Limiter.Wait?
basics
~20 sAllow never blocks: it takes a token if one is free and returns true, otherwise false, so the caller must shed or retry. Wait blocks until a token is free, the context is cancelled, or its deadline passes.
How does a buffered `chan struct{}` limit how many goroutines do work at the same time?
basics
~20 sA buffered channel of capacity N holds N permits. Sending an empty struct takes a permit and blocks once N are outstanding; a receive in a defer gives it back. At most N goroutines run the guarded work.
Two goroutines run `counter++` on the same shared variable with no lock — why is that a data race?
basics
~20 scounter++ is a load, an add and a store, not one step. Two goroutines can load the same old value and store the same result, losing an update. Unsynchronized concurrent access with at least one writer is a data race.
Why does one goroutine running `ch := make(chan int); ch <- 1; v := <-ch` block forever on the send?
basics
~20 sAn unbuffered channel holds nothing, so a send blocks until another goroutine is ready to receive. The only receive here is the next line of the same goroutine, which cannot run until the send finishes.
What does go test -race do, and what does its DATA RACE report contain?
basics
~20 sgo test -race builds an instrumented test binary that watches memory accesses while the tests run. When two goroutines touch the same memory unsynchronized and at least one writes, it prints WARNING: DATA RACE with a stack for each access, and the test fails.
In a Go test, what should replace time.Sleep when waiting for a goroutine to finish?
basics
~20 sLet the goroutine signal on a channel and have the test receive from it, so the test unblocks exactly when the work is done. Add a select with a time.After case so a hang fails the test instead of stalling it.
Why does sync.WaitGroup.Wait hang forever when a worker goroutine returns early on an error?
basics
~20 sA WaitGroup is only a counter: Add raises it, Done lowers it, Wait blocks until it reaches zero. A worker that returns before reaching its Done leaves the counter above zero, so Wait never unblocks. Put Done in a defer.
In a Go service, why must Submit stop accepting new records before shutdown calls sync.WaitGroup.Wait?
basics
~20 sA sync.WaitGroup's Wait returns the moment its counter reaches zero, which can happen in a lull between two submissions. Refusing new work first makes the counter only fall, so Wait returning really means nothing is left in flight.
A `go worker()` call returns no value, so how does a supervisor goroutine learn that the worker exited and why?
basics
~20 sA goroutine cannot return anything to whoever started it. The usual answer is a channel: a small wrapper goroutine calls the worker function and sends its returned error on a chan error, and the supervisor waits on that channel.
A Go daemon's ticker fires every 30s but each renewal run takes 90s. What happens to the ticks in between?
basics
~20 sThey are dropped. The loop body is sequential, so nothing receives while the run is in progress, and a time.Ticker keeps at most one pending tick rather than a backlog. The daemon quietly settles at one run every 90 seconds.
In what order does a Go service tear down its subsystems, and who closes each input channel?
basics
~20 sTear down in the direction data flows: stop accepting, let each sending goroutine return, close its output channel from that sending side, wait for the receivers to drain it, then release shared resources like a flusher last.
Why does a Go loop driven by time.NewTicker do nothing for a whole interval after it starts?
basics
~20 sA ticker delivers its first value one full interval after it is created, never at time zero. To act at startup, do the work once before the loop, or put the work first in the loop body and the tick wait last.