Channel synchronization in Go - Time & Space Complexity
When using channels in Go, it's important to know how the program's speed changes as more data passes through. We want to see how waiting for communication affects the time it takes.
How does the program's running time grow when more messages are sent and received through channels?
Analyze the time complexity of the following code snippet.
package main
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 1000; i++ {
ch <- i
}
close(ch)
}()
for val := range ch {
_ = val
}
}
This code sends 1000 integers through a channel from one goroutine to the main goroutine, which receives them one by one.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Sending and receiving each integer through the channel.
- How many times: Exactly 1000 times, once per integer.
Each message sent must be received, so the total work grows directly with the number of messages.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 sends and 10 receives |
| 100 | About 100 sends and 100 receives |
| 1000 | About 1000 sends and 1000 receives |
Pattern observation: The total steps increase evenly as the number of messages grows.
Time Complexity: O(n)
This means the time to send and receive messages grows in a straight line with the number of messages.
[X] Wrong: "Channels add a fixed delay, so time stays the same no matter how many messages are sent."
[OK] Correct: Each message requires a send and receive step, so more messages mean more total time.
Understanding how channel communication scales helps you write efficient concurrent programs and explain your reasoning clearly in interviews.
"What if we used a buffered channel with capacity 1000 instead of an unbuffered channel? How would the time complexity change?"
Practice
Solution
Step 1: Understand unbuffered channel behavior
Unbuffered channels require both sender and receiver to be ready at the same time for communication.Step 2: Analyze sending on unbuffered channel
If no goroutine is ready to receive, the sending goroutine blocks and waits until a receiver is ready.Final Answer:
The sending goroutine waits until another goroutine receives the data. -> Option AQuick Check:
Unbuffered channel send blocks until receive [OK]
- Thinking data is buffered automatically
- Assuming send never blocks
- Believing program crashes on send
Solution
Step 1: Recall channel creation syntax
Channels are created using make with type and optional buffer size: make(chan Type, capacity).Step 2: Identify correct buffered channel syntax
To create a buffered channel of int with capacity 3, use make(chan int, 3).Final Answer:
ch := make(chan int, 3) -> Option CQuick Check:
Buffered channel syntax = make(chan Type, capacity) [OK]
- Omitting buffer size for buffered channel
- Using new() instead of make()
- Incorrect channel literal syntax
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
ch <- "hello"
}()
msg := <-ch
fmt.Println(msg)
}Solution
Step 1: Analyze channel and goroutine
A goroutine sends "hello" on an unbuffered channel ch, while main receives from ch.Step 2: Understand synchronization
The send and receive synchronize, so main receives "hello" and prints it.Final Answer:
hello -> Option DQuick Check:
Send and receive synchronize, printing message [OK]
- Expecting deadlock because of goroutine
- Thinking channel is buffered by default
- Assuming no output due to concurrency
package main
func main() {
ch := make(chan int)
ch <- 5
val := <-ch
println(val)
}Solution
Step 1: Check channel creation and usage
Channel ch is unbuffered and created correctly with make(chan int).Step 2: Analyze send operation
Sending on unbuffered channel blocks until another goroutine receives. Here, send happens before receive, no other goroutine receives, so it blocks forever.Final Answer:
Sending on channel blocks forever causing deadlock. -> Option BQuick Check:
Send blocks without concurrent receiver [OK]
- Thinking receive blocks first
- Assuming channel is nil
- Believing code runs without deadlock
Solution
Step 1: Understand synchronization goal
Main must wait for all 3 goroutines to finish before exiting.Step 2: Analyze channel usage options
Buffered channel with capacity 3 allows all goroutines to send done signals without blocking; main receives 3 signals to know all finished.Final Answer:
Use a buffered channel with capacity 3; each goroutine sends a signal when done; main receives 3 signals. -> Option AQuick Check:
Buffered channel capacity = number of goroutines to sync [OK]
- Using unbuffered channel causing deadlock
- Receiving fewer signals than goroutines
- Not waiting for goroutines at all
Ready to prove it?
Pass the test and this pattern moves unknown
