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

DushyanthDushyanth
2 min read

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 channel data.

  • 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:

Loopfor 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)
}
0
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.