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 data
listens 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 data
still 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
<-data
again — 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)
}
Subscribe to my newsletter
Read articles from Dushyanth directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Dushyanth
Dushyanth
A Full Stack Developer with a knack for creating engaging web experiences. Currently tinkering with GO.