How to Wait for Goroutines to Finish with sync.WaitGroup

Use `sync.WaitGroup` to synchronize goroutines by incrementing its counter before launching each one and calling `Done()` when they finish, then block the main thread with `Wait()` until the counter reaches zero.

Use sync.WaitGroup to synchronize goroutines by incrementing its counter before launching each one and calling Done() when they finish, then block the main thread with Wait() until the counter reaches zero. This ensures the program doesn't exit prematurely while background tasks are still running.

Here is a practical example where the main function waits for three worker goroutines to complete their tasks:

package main

import (
	"fmt"
	"sync"
	"time"
)

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done() // Must be called before the function returns
	fmt.Printf("Worker %d starting\n", id)
	time.Sleep(1 * time.Second) // Simulate work
	fmt.Printf("Worker %d done\n", id)
}

func main() {
	var wg sync.WaitGroup

	// Launch 3 goroutines
	for i := 1; i <= 3; i++ {
		wg.Add(1) // Increment counter before starting
		go worker(i, &wg)
	}

	// Block until all workers call Done()
	wg.Wait()
	fmt.Println("All workers finished")
}

A common mistake is calling wg.Add() after the goroutine has already started, which can cause a race condition if the goroutine finishes before the counter is incremented. Always call Add() before go. Additionally, ensure Done() is called exactly once per Add(). Using defer wg.Done() at the start of the goroutine function is the safest pattern, as it guarantees the counter decrements even if the function panics or returns early.

If you need to wait for a dynamic number of goroutines (e.g., based on a slice length), calculate the count first, then loop to launch them:

tasks := []string{"task1", "task2", "task3"}
var wg sync.WaitGroup

for _, t := range tasks {
	wg.Add(1)
	go func(task string) {
		defer wg.Done()
		fmt.Println("Processing:", task)
	}(t)
}
wg.Wait()

This approach scales cleanly regardless of how many tasks you have. Remember that Wait() is blocking; if you need non-blocking checks, you'd typically use channels instead, but for standard "wait for all" scenarios, WaitGroup is the idiomatic and efficient choice.