Irbisa · Cheatsheet
Goroutines & Channels
Channels, select, worker pools, goroutine leaks, errgroup
What is a goroutine, and how does it differ from an OS thread?
What is the difference between concurrency and parallelism?
What happens to goroutines that are still running when
mainreturns, and how do you makemainwait for them?func main() { var wg sync.WaitGroup for i := range 3 { wg.Add(1) // before `go`, in the parent go func() { defer wg.Done()…How do you get a result out of a goroutine, given that
go f()throws its return values away?func squares(nums []int) []int { results := make([]int, len(nums)) var wg sync.WaitGroup for i, n := range nums { wg.Add(1) go func() {…What is the difference between buffered and unbuffered channels, and when does a send or a receive block on each?
func main() { unbuf := make(chan int) go func() { unbuf <- 1 }() // waits until main receives fmt.Println(<-unbuf) // 1 buf := make(chan int, 2)…What happens when you send to, receive from or close a closed channel, and what happens with a nil channel?
func main() { ch := make(chan int, 2) ch <- 1 ch <- 2 close(ch)…How does
selectwork: what happens when several cases are ready, what isdefaultfor, and how do you add a timeout?func receive(ctx context.Context, ch <-chan Msg) (Msg, error) { select { case m := <-ch: return m, nil case <-time.After(time.Second): return Msg{}, errors.New("timeout")…What does this program print, and why does the runtime report a deadlock here but not in a hung production service?
func main() { ch := make(chan int, 3) for i := 1; i <= 3; i++ { ch <- i } for v := range ch { fmt.Println(v) } fmt.Println("done")}Who should close a channel, and what do the directional types
<-chan Tandchan<- Tadd?// The stage owns its output: it creates it, sends on it and closes it.func square(in <-chan int) <-chan int { out := make(chan int) go func() { defer close(out) // the only sender closes, once for v := range in {…This program is meant to print 1000. What does it actually print, and how do you fix it?
func main() { var count int var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer wg.Done() count++ }() } wg.Wait() fmt.Println(count)}Find the bug:
fetchAllreturns empty strings for pages that downloaded fine.func fetchAll(urls []string) []string { var wg sync.WaitGroup bodies := make([]string, len(urls)) for i, u := range urls { go func() { wg.Add(1) defer wg.Done() bodies[i] = fetch(u) }() } wg.Wait() return bodies}How is a channel implemented in the runtime, and what happens to a goroutine that blocks on a send?
When do you use a channel and when a mutex, and why is "share memory by communicating" not always the answer?
What is a goroutine leak, what typically causes one, and how do you detect it?
// wrong: on timeout the handler returns and the worker parks in its send foreverfunc handle(w http.ResponseWriter, r *http.Request) { out := make(chan Result) // right: make(chan Result, 1) go func() { out <- compute(r.Context()) }() select {…What is the difference between
time.After,time.NewTimer,time.NewTickerandtime.AfterFunc, and what changed for timers in Go 1.23?func relay(ctx context.Context, in <-chan Msg) error { const idle = 5 * time.Minute t := time.NewTimer(idle) // one timer for the whole loop defer t.Stop() for {…How do you implement a worker pool in Go, collect the results and shut the workers down cleanly?
func process(ctx context.Context, jobs []Job, n int) []Result { in := make(chan Job) out := make(chan Result) var wg sync.WaitGroup for range n {…How do you limit how many goroutines run at once, and how does that differ from a worker pool?
func uploadAll(ctx context.Context, items []Item) error { sem := make(chan struct{}, 8) var wg sync.WaitGroup for _, it := range items { select { case sem <- struct{}{}: // acquire before go: the loop waits here…How do you merge N channels into one (fan-in), and what happens if one of the inputs never closes?
func Merge[T any](ctx context.Context, ins ...<-chan T) <-chan T { out := make(chan T) var wg sync.WaitGroup for _, in := range ins { wg.Go(func() { for {…Write two goroutines that print the numbers 1 to N alternately: one prints the odd numbers, the other the even ones.
func player(from, n int, mine <-chan struct{}, next chan<- struct{}) { for i := from; i <= n; i += 2 { <-mine // wait for our turn fmt.Println(i) if i < n { // nobody is left to take the turn after the last number next <- struct{}{}…How do you run N tasks in parallel, cancel the rest on the first error and wait for all of them?
func fetchAll(ctx context.Context, urls []string) ([]Page, error) { g, ctx := errgroup.WithContext(ctx) g.SetLimit(16) // once, before the first Go pages := make([]Page, len(urls)) for i, u := range urls {…How would you implement a goroutine-safe rate limiter, and when would you use
golang.org/x/time/rateinstead?type Bucket struct { mu sync.Mutex tokens float64 burst float64 rate float64 // tokens per second last time.Time…How would you write a goroutine-safe in-memory cache with a TTL, without starting a goroutine per key?
type entry struct { val string expires time.Time} type Cache struct {…How do you build a pipeline of channel stages, and how do the stages shut down when the consumer stops reading early?
func gen(ctx context.Context, nums ...int) <-chan int { out := make(chan int) go func() { defer close(out) for _, n := range nums { select {…What is backpressure in a channel pipeline, what does a buffer actually buy you, and what do you do when the consumer is permanently slower?
Parallel workers made a stage faster but scrambled the output order. How do you restore input order without holding the whole stream in memory?
type job struct { row Row res chan Result // cap 1: the worker never waits for a slow consumer} func enrich(rows []Row, n int) <-chan Result {…