skip to content

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 pageshow

explore

questions

172 · 7 sections

In Go, what does the go keyword do when placed in front of a function call?

level: juniorimportance: must knowfreq 88%
basics
~20 s

It 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.

open as a page

What is a goroutine leak in Go, and why does the runtime never reclaim a goroutine that is blocked forever?

level: juniorimportance: must knowfreq 72%
basics
~20 s

A 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.

open as a page

How do you stop a goroutine you started in Go, given the runtime offers no kill call?

level: juniorimportance: must knowfreq 78%
basics
~20 s

You 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.

open as a page

What happens to a Go program when a goroutine panics and nothing recovers it?

level: juniorimportance: must knowfreq 75%
basics
~20 s

The 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.

open as a page

In Go, when are the function value and arguments of go f(x) evaluated?

level: middleimportance: must knowfreq 68%
basics
~20 s

Immediately, 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.

open as a page

In Go, when does a send block on make(chan int) versus make(chan int, 3)?

level: juniorimportance: must knowfreq 82%
basics
~20 s

A 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.

open as a page

When you send a struct value on a Go channel, does the receiver get a copy or the sender's original?

level: juniorimportance: must knowfreq 65%
basics
~20 s

A 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.

open as a page

What does close(ch) do to a Go channel, and what do receivers see afterwards?

level: juniorimportance: must knowfreq 82%
basics
~20 s

close(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.

open as a page

What does a nil channel in Go do to a goroutine that sends or receives on it?

level: juniorimportance: must knowfreq 62%
basics
~10 s

Both 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.

open as a page

What does a `default` case do in a Go `select` statement?

level: juniorimportance: must knowfreq 68%
basics
~20 s

A 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.

open as a page

What does Go's atomic.Int64 give a shared counter that a plain hits++ from many goroutines does not?

level: juniorimportance: must knowfreq 68%
basics
~20 s

atomic.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.

open as a page

How do you create a sync.Cond in Go, and what does its Wait method do to c.L?

level: juniorimportance: must knowfreq 32%
basics
~20 s

sync.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.

open as a page

Why does `defer mu.Unlock()` at the top of a Go method hold the mutex longer than the data needs?

level: juniorimportance: must knowfreq 62%
basics
~20 s

A 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.

open as a page

What is Go's sync.Map, and why can't several goroutines share a plain map instead?

level: juniorimportance: must knowfreq 55%
basics
~20 s

sync.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.

open as a page

Why does a sync.Mutex need no constructor, and what is the standard Lock and Unlock pattern?

level: juniorimportance: must knowfreq 85%
basics
~20 s

A 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.

open as a page

What is context.Context in Go, and why is it passed as a function's first parameter?

level: juniorimportance: must knowfreq 85%
basics
~20 s

context.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.

open as a page

What does context.WithCancel return, and why must the cancel function always be called?

level: juniorimportance: must knowfreq 78%
basics
~20 s

context.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.

open as a page

In Go, what is the difference between context.WithTimeout and context.WithDeadline?

level: juniorimportance: must knowfreq 72%
basics
~10 s

context.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.

open as a page

Which Go standard-library calls actually stop early when the context.Context you passed them is cancelled?

level: juniorimportance: must knowfreq 72%
basics
~10 s

Only 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.

open as a page

Why should a context.WithValue key be an unexported custom type rather than a plain string?

level: juniorimportance: must knowfreq 62%
basics
~20 s

Context 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.

open as a page

What does golang.org/x/sync/errgroup add over a sync.WaitGroup for concurrent tasks that can fail?

level: juniorimportance: must knowfreq 72%
basics
~20 s

errgroup 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.

open as a page

How do you merge several receive-only Go channels into one channel a consumer can range over?

level: juniorimportance: must knowfreq 62%
basics
~20 s

Start 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.

open as a page

In a Go pipeline, why does each stage return a receive-only `<-chan Out` and close only that channel?

level: juniorimportance: must knowfreq 60%
basics
~20 s

A 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.

open as a page

In golang.org/x/time/rate, what is the difference between Limiter.Allow and Limiter.Wait?

level: juniorimportance: must knowfreq 60%
basics
~20 s

Allow 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.

open as a page

How does a buffered `chan struct{}` limit how many goroutines do work at the same time?

level: juniorimportance: must knowfreq 60%
basics
~20 s

A 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.

open as a page

Two goroutines run `counter++` on the same shared variable with no lock — why is that a data race?

level: juniorimportance: must knowfreq 82%
basics
~20 s

counter++ 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.

open as a page

Why does one goroutine running `ch := make(chan int); ch <- 1; v := <-ch` block forever on the send?

level: juniorimportance: must knowfreq 72%
basics
~20 s

An 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.

open as a page

What does go test -race do, and what does its DATA RACE report contain?

level: juniorimportance: must knowfreq 72%
basics
~20 s

go 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.

open as a page

In a Go test, what should replace time.Sleep when waiting for a goroutine to finish?

level: juniorimportance: must knowfreq 62%
basics
~20 s

Let 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.

open as a page

Why does sync.WaitGroup.Wait hang forever when a worker goroutine returns early on an error?

level: middleimportance: must knowfreq 66%
basics
~20 s

A 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.

open as a page

In a Go service, why must Submit stop accepting new records before shutdown calls sync.WaitGroup.Wait?

level: juniorimportance: must knowfreq 60%
basics
~20 s

A 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.

open as a page

A `go worker()` call returns no value, so how does a supervisor goroutine learn that the worker exited and why?

level: juniorimportance: must knowfreq 70%
basics
~20 s

A 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.

open as a page

A Go daemon's ticker fires every 30s but each renewal run takes 90s. What happens to the ticks in between?

level: middleimportance: must knowfreq 58%
basics
~20 s

They 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.

open as a page

In what order does a Go service tear down its subsystems, and who closes each input channel?

level: middleimportance: must knowfreq 64%
basics
~20 s

Tear 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.

open as a page

Why does a Go loop driven by time.NewTicker do nothing for a whole interval after it starts?

level: juniorimportance: should knowfreq 52%
basics
~20 s

A 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.

open as a page