Bird
Raised Fist0
Goprogramming~10 mins

Common concurrency patterns in Go - Step-by-Step Execution

Choose your learning style10 modes available
Concept Flow - Common concurrency patterns
Start Goroutine
↓
Perform Task Concurrently
↓
Communicate via Channel
↓
Synchronize with WaitGroup
↓
Collect Results
↓
Main Goroutine Continues
This flow shows how Go runs tasks concurrently using goroutines, communicates results through channels, and synchronizes completion with WaitGroups.
Execution Sample
Go
package main
import (
 "fmt"
 "sync"
)
func main() {
 var wg sync.WaitGroup
 ch := make(chan string)
 wg.Add(1)
 go func() {
  defer wg.Done()
  ch <- "Hello from goroutine"
 }()
 go func() {
  wg.Wait()
  close(ch)
 }()
 for msg := range ch {
  fmt.Println(msg)
 }
}
This Go program starts a goroutine that sends a message through a channel, waits for it to finish, then closes the channel so the main goroutine can print the message.
Execution Table
StepActionGoroutine StateChannel StateWaitGroup CounterOutput
1Main starts, creates WaitGroup and channelMain runningEmpty0
2Main adds 1 to WaitGroupMain runningEmpty1
3Start goroutine to send messageGoroutine startedEmpty1
4Goroutine sends "Hello from goroutine" to channelGoroutine blocked on sendEmpty (send blocked)1
5Main starts goroutine to wait and close channelWait goroutine startedEmpty (send blocked)1
6Main reads from channelMain receives from channelEmpty after read1Hello from goroutine
7Goroutine finishes, calls Done()Goroutine finishedEmpty0
8Wait goroutine detects WaitGroup counter 0, closes channelWait goroutine finishedClosed0
9Main loop ends as channel closedMain finishedClosed0
💡 Channel closed after WaitGroup counter reached zero, main loop ends.
Variable Tracker
VariableStartAfter Step 2After Step 4After Step 6After Step 7Final
WaitGroup counter011100
Channel stateemptyemptyemptyemptyemptyclosed
Output"""""""Hello from goroutine""Hello from goroutine""Hello from goroutine"
Key Moments - 3 Insights
Why does the goroutine block when sending to the channel at step 4?
Because the channel is unbuffered and no goroutine is ready to receive yet, the send operation waits until the main goroutine reads from the channel (step 6).
Why do we need a separate goroutine to wait and close the channel?
Closing the channel after all sends are done signals the main goroutine to stop reading. The wait goroutine waits for WaitGroup counter to reach zero before closing, ensuring no sends happen after close.
What happens if we forget to call wg.Done() in the sending goroutine?
The WaitGroup counter never decreases, so the wait goroutine never closes the channel, causing the main goroutine to block forever waiting for more messages.
Visual Quiz - 3 Questions
Test your understanding
Look at the execution table, what is the WaitGroup counter after step 7?
A0
B1
C2
DUndefined
💡 Hint
Check the 'WaitGroup Counter' column at step 7 in the execution table.
At which step does the main goroutine receive the message from the channel?
AStep 4
BStep 6
CStep 8
DStep 9
💡 Hint
Look at the 'Output' column to see when the message appears.
If the channel was buffered with capacity 1, how would step 4 change?
AGoroutine would block waiting for receiver
BChannel would close immediately
CGoroutine would send without blocking
DWaitGroup counter would increase
💡 Hint
Buffered channels allow one send without waiting for a receiver.
Concept Snapshot
Go concurrency uses goroutines to run tasks at the same time.
Channels let goroutines send and receive messages safely.
WaitGroups help wait for many goroutines to finish.
Unbuffered channels block senders until receivers are ready.
Closing a channel signals no more messages will come.
Use these patterns to coordinate concurrent work safely.
Full Transcript
This visual execution trace shows how Go uses common concurrency patterns: starting goroutines, communicating via channels, and synchronizing with WaitGroups. The main goroutine creates a WaitGroup and a channel, then starts a goroutine that sends a message through the channel. Because the channel is unbuffered, the sending goroutine blocks until the main goroutine reads the message. Another goroutine waits for the WaitGroup counter to reach zero and then closes the channel, signaling the main goroutine to stop reading. The variable tracker shows how the WaitGroup counter and channel state change step by step. Key moments clarify why blocking happens on unbuffered channels, why closing channels is done after all sends, and the importance of calling Done on the WaitGroup. The quiz tests understanding of WaitGroup counters, message receipt timing, and buffered channel behavior. This pattern is essential for safe and effective concurrent programming in Go.

Practice

(1/5)
1. What is the primary purpose of a WaitGroup in Go concurrency?
easy
A. To limit the number of goroutines running at the same time
B. To send data safely between goroutines
C. To wait for a collection of goroutines to finish before continuing
D. To create new goroutines

Solution

  1. Step 1: Understand WaitGroup's role

    A WaitGroup is used to wait for multiple goroutines to complete their work.
  2. Step 2: Compare with other concurrency tools

    Channels send data, semaphores limit concurrency, and goroutines run functions concurrently.
  3. Final Answer:

    To wait for a collection of goroutines to finish before continuing -> Option C
  4. Quick Check:

    WaitGroup = wait for goroutines [OK]
Hint: WaitGroup waits for goroutines to finish, not for data passing [OK]
Common Mistakes:
  • Confusing WaitGroup with channels
  • Thinking WaitGroup limits goroutines
  • Using WaitGroup to send data
2. Which of the following is the correct syntax to start a new goroutine calling function doWork()?
easy
A. run doWork()
B. goroutine doWork()
C. start doWork()
D. go doWork()

Solution

  1. Step 1: Recall goroutine syntax

    In Go, the keyword go before a function call starts it as a goroutine.
  2. Step 2: Check options

    Only go doWork() matches the correct syntax; others are invalid keywords.
  3. Final Answer:

    go doWork() -> Option D
  4. Quick Check:

    Start goroutine with 'go' keyword [OK]
Hint: Start goroutine with 'go' keyword before function call [OK]
Common Mistakes:
  • Using non-existent keywords like 'goroutine' or 'start'
  • Omitting 'go' keyword
  • Trying to run goroutine without parentheses
3. What will be the output of this Go program?
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")
}
medium
A. Hello from goroutine 2 Hello from goroutine 1
B. Hello from goroutine 1 Hello from goroutine 2 All goroutines finished
C. Hello from goroutine 1 All goroutines finished Hello from goroutine 2
D. All goroutines finished Hello from goroutine 1 Hello from goroutine 2

Solution

  1. Step 1: Understand WaitGroup usage

    The program adds 2 to the WaitGroup, then starts 2 goroutines that each print a message and call wg.Done().
  2. 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.
  3. Final Answer:

    Hello from goroutine 1 Hello from goroutine 2 All goroutines finished -> Option B
  4. Quick Check:

    WaitGroup ensures final print after goroutines [OK]
Hint: WaitGroup.Wait blocks until all Done calls complete [OK]
Common Mistakes:
  • Assuming main prints before goroutines
  • Ignoring WaitGroup synchronization
  • Expecting goroutine prints after final print
4. Identify the error in this Go code snippet using channels:
package main
import "fmt"
func main() {
  ch := make(chan int)
  go func() {
    ch <- 10
  }()
  fmt.Println(<-ch)
  fmt.Println(<-ch)
}
medium
A. Second read from channel blocks forever
B. Channel is not closed before reading
C. Channel should be buffered to send data
D. Goroutine is not started correctly

Solution

  1. Step 1: Analyze channel sends and receives

    The goroutine sends one value (10) into the channel. The main goroutine reads twice from the channel.
  2. 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).
  3. Final Answer:

    Second read from channel blocks forever -> Option A
  4. Quick Check:

    One send, two receives causes blocking [OK]
Hint: Reads must match sends; extra read blocks forever [OK]
Common Mistakes:
  • Thinking channel must be closed before reading
  • Assuming buffered channel fixes blocking here
  • Believing goroutine not started properly
5. You want to create a worker pool in Go to process 5 jobs concurrently but limit to 2 workers at a time. Which pattern correctly implements this?
hard
A. Use a buffered channel of size 2 as a semaphore to limit goroutines, and a WaitGroup to wait for all jobs
B. Start 5 goroutines without limit and use a WaitGroup to wait
C. Use an unbuffered channel to send jobs and no WaitGroup
D. Use a mutex to lock and unlock workers

Solution

  1. 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.
  2. Step 2: Use WaitGroup to wait for all jobs

    A WaitGroup tracks all 5 jobs to ensure main waits until all finish before exiting.
  3. Final Answer:

    Use a buffered channel of size 2 as a semaphore to limit goroutines, and a WaitGroup to wait for all jobs -> Option A
  4. Quick Check:

    Semaphore + WaitGroup = controlled concurrency [OK]
Hint: Semaphore channel limits workers; WaitGroup waits for all [OK]
Common Mistakes:
  • Starting all goroutines without limit
  • Not using WaitGroup to wait for completion
  • Using mutex incorrectly for concurrency limit
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard