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

```go
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:

```go
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:

```go
Hello 0
Hello 1
Hello 2
Hello 3
```

But instead, it prints:

```go
Hello 1
Hello 3
```

Wait… what?

---

## What’s Going On?

Let’s break down the two loop variants:

### ✅ Correct Version:

```go
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:

```go
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:

```go
for value := range data {
    fmt.Println(value)
}
```

### ❌ Not this:

```go
for range data {
    fmt.Println(<-data)
}
```
