Understanding Go Channels: Why for range data Can Trip You Up

Go is known for its elegant handling of concurrency, and one of the core tools in its concurrency toolbox is the channel. But even experienced Go developers can stumble upon subtle bugs, especially when working with for-range loops and channels.
In this post, we’ll dive into a deceptively simple loop:
for range data {
fmt.Println(<-data)
}
At first glance, this might look fine. But it doesn’t behave the way you might expect. Let’s explore why.
The Setup: A Goroutine and a Channel
Here’s the full code we’re analyzing:
package main
import "fmt"
func main() {
data := make(chan string)
go func() {
for i := 0; i < 4; i++ {
data <- "Hello " + string('0'+i)
}
close(data)
}()
for range data {
fmt.Println(<-data)
}
// Correct version (commented out):
// for value := range data {
// fmt.Println(value)
// }
}
You might expect this to print:
Hello 0
Hello 1
Hello 2
Hello 3
But instead, it prints:
Hello 1
Hello 3
Wait… what?
What’s Going On?
Let’s break down the two loop variants:
✅ Correct Version:
for value := range data {
fmt.Println(value)
}
range datalistens on the channeldata.It automatically receives each value sent on the channel, assigns it to
value, and runs the loop.When the channel is closed, the loop exits.
It reads one value per iteration - perfect.
❌ The Buggy Version:
for range data {
fmt.Println(<-data)
}
This one’s sneaky.
for range datastill reads a value from the channel each iteration.But that value isn’t assigned or used - it’s just discarded.
Then, inside the loop, you manually call
<-dataagain — pulling a second value from the channel.
🚨 You're reading two values per iteration.
Let’s simulate what happens:
| Loop | for range data (ignored value) | <-data (printed value) |
| 1 | "Hello 0" | "Hello 1" |
| 2 | "Hello 2" | "Hello 3" |
| 3 | (channel is closed) | loop ends |
So only "Hello 1" and "Hello 3" get printed. The others are discarded during the range evaluation.
The Takeaway
In Go, for range already pulls values from a channel. If you also call <-data inside that loop, you're unintentionally skipping every other value!
✔️ Do this:
for value := range data {
fmt.Println(value)
}
❌ Not this:
for range data {
fmt.Println(<-data)
}





