Common concurrency patterns in Go - Time & Space Complexity
When using concurrency in Go, it's important to understand how the program's work grows as more tasks run at the same time.
We want to see how the time to finish changes when we add more concurrent parts.
Analyze the time complexity of the following code snippet.
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
// simulate work
}
func main() {
var wg sync.WaitGroup
n := 5
wg.Add(n)
for i := 0; i < n; i++ {
go worker(i, &wg)
}
wg.Wait()
fmt.Println("All workers done")
}
This code runs several workers at the same time using goroutines and waits for all to finish.
- Primary operation: Starting and running
ngoroutines concurrently. - How many times: The loop runs
ntimes to launch workers.
As we increase the number of workers, the program launches more goroutines, but they run at the same time.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | 10 goroutines started, run concurrently |
| 100 | 100 goroutines started, run concurrently |
| 1000 | 1000 goroutines started, run concurrently |
Pattern observation: The number of goroutines grows linearly with input, but total time depends on how they run together.
Time Complexity: O(n)
This means the total work grows linearly with the number of concurrent tasks started.
[X] Wrong: "Running many goroutines always makes the program faster and time stays the same."
[OK] Correct: Even if goroutines run at the same time, starting many can add overhead and the actual speed depends on CPU and synchronization.
Understanding how concurrency affects time helps you explain how programs handle many tasks and manage resources efficiently.
"What if we added a channel to limit how many goroutines run at once? How would the time complexity change?"
Practice
WaitGroup in Go concurrency?Solution
Step 1: Understand WaitGroup's role
AWaitGroupis used to wait for multiple goroutines to complete their work.Step 2: Compare with other concurrency tools
Channels send data, semaphores limit concurrency, and goroutines run functions concurrently.Final Answer:
To wait for a collection of goroutines to finish before continuing -> Option CQuick Check:
WaitGroup = wait for goroutines [OK]
- Confusing WaitGroup with channels
- Thinking WaitGroup limits goroutines
- Using WaitGroup to send data
doWork()?Solution
Step 1: Recall goroutine syntax
In Go, the keywordgobefore a function call starts it as a goroutine.Step 2: Check options
Onlygo doWork()matches the correct syntax; others are invalid keywords.Final Answer:
go doWork() -> Option DQuick Check:
Start goroutine with 'go' keyword [OK]
- Using non-existent keywords like 'goroutine' or 'start'
- Omitting 'go' keyword
- Trying to run goroutine without parentheses
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(2)
go func() {
fmt.Println("Hello from goroutine 1")
wg.Done()
}()
go func() {
fmt.Println("Hello from goroutine 2")
wg.Done()
}()
wg.Wait()
fmt.Println("All goroutines finished")
}Solution
Step 1: Understand WaitGroup usage
The program adds 2 to the WaitGroup, then starts 2 goroutines that each print a message and callwg.Done().Step 2: Analyze output order
The main goroutine waits for both goroutines to finish before printing "All goroutines finished". The two goroutine prints can appear in any order but both appear before the final print.Final Answer:
Hello from goroutine 1 Hello from goroutine 2 All goroutines finished -> Option BQuick Check:
WaitGroup ensures final print after goroutines [OK]
- Assuming main prints before goroutines
- Ignoring WaitGroup synchronization
- Expecting goroutine prints after final print
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
ch <- 10
}()
fmt.Println(<-ch)
fmt.Println(<-ch)
}Solution
Step 1: Analyze channel sends and receives
The goroutine sends one value (10) into the channel. The main goroutine reads twice from the channel.Step 2: Identify blocking behavior
The first read gets 10 successfully. The second read waits for another value that never comes, causing a deadlock (blocks forever).Final Answer:
Second read from channel blocks forever -> Option AQuick Check:
One send, two receives causes blocking [OK]
- Thinking channel must be closed before reading
- Assuming buffered channel fixes blocking here
- Believing goroutine not started properly
Solution
Step 1: Understand worker pool concurrency limit
To limit concurrent workers, a buffered channel (semaphore) of size 2 can control how many goroutines run simultaneously.Step 2: Use WaitGroup to wait for all jobs
A WaitGroup tracks all 5 jobs to ensure main waits until all finish before exiting.Final Answer:
Use a buffered channel of size 2 as a semaphore to limit goroutines, and a WaitGroup to wait for all jobs -> Option AQuick Check:
Semaphore + WaitGroup = controlled concurrency [OK]
- Starting all goroutines without limit
- Not using WaitGroup to wait for completion
- Using mutex incorrectly for concurrency limit
Ready to prove it?
Pass the test and this pattern moves unknown
