WaitGroups in Go
In the previous lesson, you learned how the select statement helps manage
multiple channel operations. In this lesson, you will learn about
WaitGroups, which are used to wait for multiple goroutines to finish
execution.
What Is a WaitGroup?
A WaitGroup is a synchronization primitive provided by the
sync package. It allows one goroutine (usually main)
to wait until a group of other goroutines have completed their work.
This is essential when launching multiple goroutines that must finish before the program exits.
Why WaitGroups Are Needed
By default, the main function does not wait for goroutines to complete. If the main function finishes, the program exits—even if goroutines are still running.
WaitGroups solve this problem by blocking execution until all goroutines signal completion.
WaitGroup Methods
Add(n)– Addsngoroutines to wait forDone()– Signals that a goroutine has finishedWait()– Blocks until the counter reaches zero
Basic WaitGroup Example
Let’s start with a simple example where the main function waits for two goroutines to finish.
package main
import (
"fmt"
"sync"
)
func task(name string, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("Task", name, "completed")
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go task("A", &wg)
go task("B", &wg)
wg.Wait()
fmt.Println("All tasks finished")
}
The program waits until both tasks call Done().
Understanding the Flow
Here’s what happens step by step:
- Main adds 2 to the WaitGroup counter
- Two goroutines start running
- Each goroutine calls
Done() - The counter reaches zero
Wait()unblocks
Real-World Example: Processing Files
Imagine processing multiple files concurrently and waiting until all files are processed.
func processFile(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("Processing file", id)
}
func main() {
var wg sync.WaitGroup
files := 5
wg.Add(files)
for i := 1; i <= files; i++ {
go processFile(i, &wg)
}
wg.Wait()
fmt.Println("All files processed")
}
This pattern is commonly used in batch jobs and data pipelines.
Using Defer with Done()
It is best practice to call Done() using defer.
This ensures the counter is decremented even if an error occurs.
func worker(wg *sync.WaitGroup) {
defer wg.Done()
// work logic here
}
WaitGroups vs Channels
WaitGroups are designed only for synchronization. They do not transfer data.
- Use WaitGroups to wait for completion
- Use channels to send and receive data
In many real systems, both are used together.
Common Mistakes
- Calling
Add()inside goroutines - Forgetting to call
Done() - Reusing a WaitGroup without resetting it
- Passing WaitGroup by value instead of pointer
WaitGroup with Loop Example
This example launches multiple goroutines dynamically.
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Println("Worker", id)
}(i)
}
wg.Wait()
Practice Exercises
Exercise 1
Create three goroutines that print messages and wait for all to finish.
Exercise 2
Modify the program to simulate delays using time.Sleep.
Key Takeaways
- WaitGroups synchronize goroutines
Add(),Done(), andWait()control execution- Always pass WaitGroup as a pointer
- Essential for concurrent workflows
What’s Next?
In the next lesson, you will learn about Mutex Locks and how to protect shared data.