So to break it down, the first question is why you're seeing 8100 printed first. That's quite simple: the routine that pushed values 0-10 to the naturals channel is not started yet by the time you reach naturals <- 90. The channels are not buffered, so that routine waits until the value 90 is read by the routine that squares the values, and only then will you be writing the values 0-10 to the channel. This isn't guaranteed to happen all the time, but starting a routine means the runtime (in particular the scheduler) has to do some work. This work takes a bit of time, during which the main routine starts another 2 routines, and writes to the channel. That's why 90 is the first value to go through the channel, rather than 0.


Next, you're noticing not all your values are printed. If you want all values to be printed, you should close, or write to the done channel when your routine printing the output has completed. instead of having done <- struct{}{} in the routine that writes to naturals, you should move that to the routine where you read from squares. To signal that the naturals and squares channels are "done", you can simply close them:

Copygo func() {
    for x := 0; x <= 10; x++ {
        naturals <- x
    }
    close(naturals) // we'll get to this in a bit
}()
go func() {
    for x := range naturals {
        squares <- x * x
    }
    close(squares)
}()
go func() {
    for x := range squares {
        fmt.Println(x)
    }
    done <- struct{}{}
}()
<-done // now we know everything has been printed
close(done) // always best to close all channels explicitly

Now this works like a treat, but we have a problem. There's no reliable, safe way to write our 90 value to the naturals channel except for moving that to the routine where we write values 0-10. You could get around that problem by re-using the done channel:

Copygo func() {
    for x := 0; x <= 10; x++ {
        naturals <- x
    }
    done <- struct{}{} // signal this routine is done
}()
go func() {
    for x := range naturals {
        squares <- x * x
    }
    close(squares)
}()
go func() {
    for x := range squares {
        fmt.Println(x)
    }
    close(done) // done channel is closed
}()
<-done // now we know everything has been printed
naturals <- 90 // now we can write to this channel
close(naturals) // signal to the square routine that was the last value
<-done // wait for the channel to close

This works, and will print the values in the order you want/expect, with 8100 as the last value, but it's a bit of a mess. Relying on a channel like done here to mean different things is a bit of a pain. We're using it to signal both that we've written all our values to the naturals channel, and then the second time to indicate that we've printed all values. That's smelly. Thankfully, we have other means of knowing when one or more routines have finished: sync.WaitGroup:

Copywg := &sync.WaitGroup{}
wg.Add(1)
go func() {
    defer wg.Done()
    for x := 0; x <= 10; x++ {
        naturals <- x
    }
}()
go func() {
    for x := range naturals {
        squares <- x * x
    }
    close(squares)
}()
go func() {
    for x := range squares {
        fmt.Println(x)
    }
    close(done) // just close it
}()
wg.Wait() // wait for the first routine to return
naturals <- 90 // now we can write to naturals
close(naturals) // we're done, let the squares routine return, and close its channel
// this in turn will trigger the printer routine to return
<- done // wait for everything to be printed

There's a lot more that can be said about this particular use of channels, who "owns" them, and what routine is responsible for closing which channel, why using buffered channels here might be a good idea (depending on what you're actually wanting to do/accomplish), etc... for now, though, this should help you get going and hopefully shed some light on what's going on.

Answer from Elias Van Ootegem on Stack Overflow
🌐
Go by Example
gobyexample.com › range-over-channels
Go by Example: Range over Channels
In a previous example we saw how for and range provide iteration over basic data structures. We can also use this syntax to iterate over values received from a channel · We’ll iterate over 2 values in the queue channel
🌐
Medium
medium.com › @AlexanderObregon › for-range-over-channels-in-go-and-what-it-does-f6847e10438c
For Range Over Channels in Go and What It Does | Medium
November 6, 2025 - A common way to read from them is with the for range loop, which handles the process of pulling values until none remain. Behind this compact syntax, the runtime coordinates communication between senders and receivers, decides when the loop should exit, and manages what happens if a channel is closed while values are still buffered. I publish free articles like this daily, and I have a Golang ...
🌐
Devtrovert
blog.devtrovert.com › select & for range channel in go: breaking down
Select & For Range Channel in Go: Breaking Down
March 12, 2024 - If you missed that discussion, it’s important to note that we identified these types to provide a detailed understanding. Yet, at the most basic level, Go channels are primarily either buffered or unbuffered. Now, we’re focusing on select and for range in Go.
Top answer
1 of 3
4

So to break it down, the first question is why you're seeing 8100 printed first. That's quite simple: the routine that pushed values 0-10 to the naturals channel is not started yet by the time you reach naturals <- 90. The channels are not buffered, so that routine waits until the value 90 is read by the routine that squares the values, and only then will you be writing the values 0-10 to the channel. This isn't guaranteed to happen all the time, but starting a routine means the runtime (in particular the scheduler) has to do some work. This work takes a bit of time, during which the main routine starts another 2 routines, and writes to the channel. That's why 90 is the first value to go through the channel, rather than 0.


Next, you're noticing not all your values are printed. If you want all values to be printed, you should close, or write to the done channel when your routine printing the output has completed. instead of having done <- struct{}{} in the routine that writes to naturals, you should move that to the routine where you read from squares. To signal that the naturals and squares channels are "done", you can simply close them:

Copygo func() {
    for x := 0; x <= 10; x++ {
        naturals <- x
    }
    close(naturals) // we'll get to this in a bit
}()
go func() {
    for x := range naturals {
        squares <- x * x
    }
    close(squares)
}()
go func() {
    for x := range squares {
        fmt.Println(x)
    }
    done <- struct{}{}
}()
<-done // now we know everything has been printed
close(done) // always best to close all channels explicitly

Now this works like a treat, but we have a problem. There's no reliable, safe way to write our 90 value to the naturals channel except for moving that to the routine where we write values 0-10. You could get around that problem by re-using the done channel:

Copygo func() {
    for x := 0; x <= 10; x++ {
        naturals <- x
    }
    done <- struct{}{} // signal this routine is done
}()
go func() {
    for x := range naturals {
        squares <- x * x
    }
    close(squares)
}()
go func() {
    for x := range squares {
        fmt.Println(x)
    }
    close(done) // done channel is closed
}()
<-done // now we know everything has been printed
naturals <- 90 // now we can write to this channel
close(naturals) // signal to the square routine that was the last value
<-done // wait for the channel to close

This works, and will print the values in the order you want/expect, with 8100 as the last value, but it's a bit of a mess. Relying on a channel like done here to mean different things is a bit of a pain. We're using it to signal both that we've written all our values to the naturals channel, and then the second time to indicate that we've printed all values. That's smelly. Thankfully, we have other means of knowing when one or more routines have finished: sync.WaitGroup:

Copywg := &sync.WaitGroup{}
wg.Add(1)
go func() {
    defer wg.Done()
    for x := 0; x <= 10; x++ {
        naturals <- x
    }
}()
go func() {
    for x := range naturals {
        squares <- x * x
    }
    close(squares)
}()
go func() {
    for x := range squares {
        fmt.Println(x)
    }
    close(done) // just close it
}()
wg.Wait() // wait for the first routine to return
naturals <- 90 // now we can write to naturals
close(naturals) // we're done, let the squares routine return, and close its channel
// this in turn will trigger the printer routine to return
<- done // wait for everything to be printed

There's a lot more that can be said about this particular use of channels, who "owns" them, and what routine is responsible for closing which channel, why using buffered channels here might be a good idea (depending on what you're actually wanting to do/accomplish), etc... for now, though, this should help you get going and hopefully shed some light on what's going on.

2 of 3
1

You probably want something like this:

Copypackage main

import (
    "fmt"
)

func main() {
    naturals := make(chan int)
    squares := make(chan int)
    done := make(chan struct{})
    doneSending := make(chan struct{})

    // counter
    go func() {
        for x := 0; x <= 10; x++ {
            naturals <- x
        }
        doneSending <- struct{}{}
    }()

    // squarer
    go func() {
        for x := range naturals {
            squares <- x * x
        }
        close(squares)
    }()

    // printer
    go func() {
        for x := range squares {
            fmt.Println(x)
        }
        done <- struct{}{}
    }()

    <-doneSending
    naturals <- 90

    close(naturals)
    <-done
}

Playground

They key is to have a chain of steps and make sure each one completes before signalling the next that it is done. Same for the entire program: don't exit before the last one is done.


Note however, that channels are a synching mechanism. An unbuffered channel can only be written to or be read from when there is a routine on the other end reading or writing to it "in that moment" (it is a bit more complex).

If a goroutine comes to such a point that it needs to wait, the Go runtime will most likely perform a context switch to another goroutine that is ready to be executed. A context switch has a certain cost and is one of the factors why using goroutines to perform tasks in parallel is often slower than just doing all in one routine. One way to reduce the number of context switches is to have buffered channels. That sacrifices a bit of memory (buffer) so that a gourine can write to a channel e.g. 10 messages (when the buffer is 10) before being blocked if the reading goroutine is not yet ready to read from it. And the reading routine can read all 10 messages from the buffer before it is blocked again. If both are reading and writing at the same time equally fast with a few items in the buffered channel, a context switch can theoretically be avoided entirely. (Still the Go runtime will perform context switches to give other goroutines a chance to run their code, given that there are other goroutines waiting)

🌐
Reddit
reddit.com › r/golang › beginner question about channels
r/golang on Reddit: Beginner question about channels
August 31, 2023 -

Lately I've been learning go and I'm really liking it so far. I think Go fits the needs of a JavaScript programmer that wants to go lower level but not as low as c/c++.Currently I'm using Go by example for learning purposes.

I was learning channels and it's taking me a while to wrap my head around it.

I'm not understanding why the following code with channels works they way it does.

Part 1 taken from Range over channels section

Part 2 taken from Worker pools section

I modified the Part 1 and instead of closing the channel before iterating over it, I closed it after the iteration, resulting in a "all goroutines are asleep - deadlock!" error message in runtime. It looks like you cant iterate over a channel that's not closed. Fair enough.

Then I stumbled upon worker pools and in the example the worker is iterating over jobs channel even though its not closed?

Clearly my understanding of channels is not correct.

Why is Part 2 working well and the modified version of Part 1 (iterate over a channel that's not closed) is not?

Thank you in advance

Top answer
1 of 5
6
If you do this package main import "fmt" func main() { queue := make(chan string, 2) queue <- "one" queue <- "two" for elem := range queue { fmt.Println(elem) } close(queue) } Then the for loop never knows to stop (it will stop when the channel it ranges over is closed and it has completed "draining" the channel, but the close channel command is never reached so the loop sits there forever.) package main import ( "fmt" "time" ) func worker(id int, jobs <-chan int, results chan<- int) { for j := range jobs { fmt.Println("worker", id, "started job", j) time.Sleep(time.Second) fmt.Println("worker", id, "finished job", j) results <- j * 2 } } func main() { const numJobs = 5 jobs := make(chan int, numJobs) results := make(chan int, numJobs) for w := 1; w <= 3; w++ { go worker(w, jobs, results) } for j := 1; j <= numJobs; j++ { jobs <- j } close(jobs) for a := 1; a <= numJobs; a++ { <-results } } Here the jobs channel close command is reached because the loop immediately before it is limited by the numJobs variable (so there's no infinite loop there) The loop inside the worker function will eventually stop because the close command for jobs is in a different goroutine, meaning that the worker loop doesn't prevent the close from happening. Edit: The easiest way to think about this is to think of it like this. In the first example the goroutine worked its way through the code, and started the loop that went forever waiting for someone to tell it to stop looping. The command to stop was the thing that it was supposed to do after the loop finished, so it could never get there. Now, the second example, the worker goroutine was sent off to do some work by the main/boss goroutine, and it went into a loop that will stop when the channel closes, it can never close the channel, and there is no other way for the loop to end. However the main goroutine, the one that sent the worker off, it has one loop to execute, that is limited by the numJobs variable, and then it closes the jobs channel, telling the worker goroutine that the loop it is in can finish.
2 of 5
3
It looks like you cant iterate over a channel that's not closed. Fair enough. Sure you can. If you range over a channel, the loop will block until (1) there's something to receive, and then run the body, or (2) the channel is closed, and then proceed after the body. That's all. I closed it after the iteration, resulting in a "all goroutines are asleep - deadlock!" It's indicating to you that no progress can be made. Since the channel is not closed, the for loop is blocked waiting for (1) another element, or (2) the channel is closed, as above. Since there's nothing else that's capable of doing either 1 or 2, you have a deadlock.
🌐
Scaler
scaler.com › home › topics › golang › iterate over channel in golang
Iterate over Channel in Golang - Scaler Topics
May 4, 2023 - To iterate over a channel, you can use the range keyword for a loop. This will repeatedly receive values from the channel until it is closed. Learn more on Scaler Topics.
Find elsewhere
🌐
Boldly Go
boldlygo.tech › archive › 2024-06-24-iteration-over-channels
Iteration over channels - Boldly Go
For channels, the iteration values produced are the successive values sent on the channel until the channel is closed. If the channel is nil, the range expression blocks forever.
🌐
Antonz
antonz.org › go-concurrency › channels
Gist of Go: Channels
December 3, 2024 - But channels have many more interesting features. Let's dive in! ... str := "one,two,,four" in := make(chan string) go func() { // (1) words := strings.Split(str, ",") for _, word := range words { in <- word } }() for { // (2) word := <-in if word != "" { fmt.Printf("%s ", word) } } // one two four
🌐
Imil
imil.net › blog › posts › 2019 › understanding-golang-channel-range-again
Understanding golang channel range... again - iMil.net
The goal here is to retrieve channel messages that are pushed from go routines created in a for loop. The most naive thought would be this piece of (non working) code: package main import "fmt" func main() { c := make(chan string) for _, t := range []string{"a", "b", "c"} { go func(s string) { c <- s }(t) } for s := range c { fmt.Println(s) } }
🌐
KodeKloud Notes
notes.kodekloud.com › docs › Advanced-Golang › Concurrency › Channels-for-range › page
Channels for range - KodeKloud
Learn to use a for-range loop in Go for iterating over channels and arrays while preventing deadlocks by closing channels properly.
🌐
Medium
medium.com › better-programming › manging-go-channels-with-range-and-close-98f93f6e8c0c
How to Manage Go Channels With Range and Close | by Abhishek Gupta | Better Programming
April 22, 2020 - To run, simply uncomment f4() in main, and run the program using go run channels-range-close.go. producer finished consumer started i = 1 i = 2 i = 3 i = 4 i = 5 consumer finished. press ctrl+c to exit · The producer goroutine finished sending five records. The consumer woke up after a while, receiving and printing out all e five messages sent by the producer. That’s all for now. Stay tuned for upcoming articles in the series! Go · Programming · Coding · Golang ·
🌐
GUI
golangr.com › range-channel
Range channel | Learn Go Programming
By doing so, it will iterate over every item thats send on the channel. You can iterate on both buffered and unbuffered channels, but buffered channels need to be closed before iterating over them. The range keyword can be used on a buffered channel. Suppose you make a channel of size 5 with ...
🌐
Go Tutorial
golangbot.com › channels
Go (Golang) Channels Tutorial with Examples | golangbot.com
October 15, 2021 - This program prints, Received 0 ... 7 true Received 8 true Received 9 true · The for range form of the for loop can be used to receive values from a channel until it is closed....
🌐
DEV Community
dev.to › itnext › how-to-manage-go-channels-using-range-and-close-2k7f
How to manage Go channels using range and close - DEV Community
May 8, 2020 - close makes it possible to signal to consumers of a channel that there is nothing else which will be sent on this channel · Let's refactor the program. First, change the consumer to use range - remove the i := <-c bit and replace it with for i := range c
🌐
Medium
mipsmonsta.medium.com › deadlock-beware-using-channel-and-range-in-golang-ff5aa9f99521
Deadlock Beware: Using Channel and Range in Golang | by Mipsmonsta | Medium
February 3, 2022 - In this case example above, the single go routine did run to completion, since every time a new sequence value is sent via the channel, the range feature will pull out the value on the other end. Thus, the error that “all go routines are asleep” is not surprising.
🌐
LinkedIn
linkedin.com › pulse › channels-golang-sagar-patil
Channels in Golang
June 13, 2023 - In this article, I will provide ... in Golang. Channels are an essential feature in Go that facilitate safe communication and synchronization between goroutines, enabling them to exchange values efficiently. I will cover topics such as understanding channels, defining channels, passing channels to goroutines, buffered and unbuffered channels, receiving values from channels, checking if a channel is open or closed, closing channels, detecting closed channels with range loops, using ...
🌐
Go
go.dev › tour › concurrency › 3
Buffered Channels
Buffered Channels · Range and Close · Select · Default Selection · Exercise: Equivalent Binary Trees · Exercise: Equivalent Binary Trees · sync.Mutex · Exercise: Web Crawler · Where to Go from here... Channels can be buffered. Provide the buffer length as the second argument to make to initialize a buffered channel: ch := make(chan int, 100) Sends to a buffered channel block only when the buffer is full.
🌐
LabEx
labex.io › tutorials › range-over-channels-15496
Range Over Channels in Golang | LabEx
// In a [previous](range) example we saw how `for` and // `range` provide iteration over basic data structures. // We can also use this syntax to iterate over // values received from a channel. package main import "fmt" func main() { // We'll iterate over 2 values in the `queue` channel.
🌐
Smartscribs
smartscribs.com › home › golang concurrency: select and for range channel
Golang Concurrency: Select and For Range Channel - Smartscribs
December 22, 2023 - There is a common trap, if you forget to close the channel, the loop will just keep waiting and it’ll be stuck, waiting for new values forever (deadlock). So, if you’re running a bunch of goroutines, this mistake can eat up memory pretty fast. So there were some of the usages, common errors and patterns to use the select & for range statement with channnels in go. I hope you are enjoying this series on golang and concurreny.