Unless there's another branch of the select, use the following:

Copyfor v := range ch {
    // do some stuff
}

The code is simpler and easier to understand than the for/select presented in the question.

If you need to do the receive inside the loop for some reason, then use the following code:

Copy for  { 
     // do some stuff
     v, ok := <-ch
     if !ok { 
        break 
     } 
     // do some other stuff
 }

As a rule of thumb, single branch select statements should be avoided. A select with a single branch is functionally the same as the branch alone.

Answer from user5728991 on Stack Overflow
🌐
WONJOON.LOG
wnjoon.github.io › 2025 › 03 › 09 › go-channel-iteration
Understanding Channel Iteration in Go: for range vs. select vs ...
March 9, 2025 - Use for range when working with a single channel that continuously receives values. Use select when handling multiple channels or managing timeouts.
Discussions

goroutine - Selecting inside range over go channel - Stack Overflow
I'm following this post to parallelise my app. I need to tailor this code: func sq(done More on stackoverflow.com
🌐 stackoverflow.com
Question about ranging over channels
you should use the range option. the first example will continue to print "" after the channel is closed on every iteration of the loop due to the behavior of closed channels . the range option works as expected since "the iteration values produced are the successive values sent on the channel until the channel is closed" from the range clause spec . More on reddit.com
🌐 r/golang
4
6
September 11, 2017
Question: multiple channels + select vs one channel of interface
A priori, if you have n operations that can be executed without blocking each other you should probably use a sync.WaitGroup along with n different go routines each listening to one of these n different channels for maximum performance and extendability. On the other hand, if you’re reading n channels in a for-select statement how do you deal with closed channels? Note that case v, ok := <- closedCh: if !ok { continue } ... is entered very often and thus raises a bad performance. That's why one usually merges n channels into one. This single channel is then closed as soon as the last of n channels has been closed. More on reddit.com
🌐 r/golang
10
6
January 11, 2021
go - select with single case blocks, adding default: unblocks - Stack Overflow
Select is required, if you have multiple goroutines to handle. The tour has an example of range and close. Another case for select would be idempotent channel closes: https://play.golang.org/p/_Ol42BvuuS. More on stackoverflow.com
🌐 stackoverflow.com
🌐
Devtrovert
blog.devtrovert.com › select & for range channel in go: breaking down
Select & For Range Channel in Go: Breaking Down - Devtrovert
March 12, 2024 - By doing this, you avoid keeping the loop running for no reason, and you manage channel activity in a clean, efficient way. ... Without setting ch1 to nil, the output continuously displays Received from ch1, and it keeps repeating because it’s no longer receiving meaningful data, just the default over and over. This continues until ch2 closes and timeout happens. Using for range with channels in Go is quite handy, instead of repeatedly checking if there’s a new value in the channel, now you can just sit back and let the loop handle it.
🌐
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.
Top answer
1 of 2
1
  1. Being in a range or not doesn't have any impact on what select is doing here.

  2. No, select doesn't take the first true expression... it doesn't take expressions at all. The only things that can appear as the cases of an expression are channel sends, channel receives, and assignments with channel receives on their right side.

    select {
    case out <- n * n:
    case <-done:
        return
    }
    

says "if sending on out is possible (i.e. it has remaining capacity or an active reader), then send the value n * n to it and continue. If receiving from done is possible, return from the function. If both are possible, choose one at random and do it. If neither is possible, wait until one of them becomes possible." (See Select Statements in the spec).

If the value you want to send needs to be computed (and it's too complex to put on the right hand side of the channel send), simply do it before the select. The spec makes it clear that all of the expressions in send statements in a select are computed ahead of time anyway, so nothing is lost.

2 of 2
0

I don't fully understand the line case out <- n * n:. I can see it's saying that if there's a value for n, then square it and send it down the channel, but I don't understand why.

That's not correct. case out <- n * n checks to see if out is ready to read, and sends n * n to out if it is. Unless done is also ready.

select is used when you have multiple channels to talk to. Whichever channel is ready, it will do that case. If multiple channels are ready, it will select one at random.

select {
    case out <- n * n:
    case <-done:
        return
    }
}

This will select over out and done. If either is ready to proceed, ie. out is ready to read or there's something to read from done, it will pick one of those cases. The order is random, so it is possible to send more down out even if there's something to be read from done.

This pattern is used to shut down infinite goroutines. If you stop reading from its output channel, it won't do any more work, but it will hang around in memory. So by passing a value to done you can tell the goroutine to shut down.


UPDATE: In your original case, where the goroutine is looping over an input channel and sending output, done is an unnecessary complication. Once the input channel is closed, the function will return.

func sq(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for n := range in {
            out <- n * n
        }
    }()
    return out
}

func main() {
    in := make(chan int)
    out := sq(in)
    for _,i := range []int{1,2,3,4} {
        in <- i
        fmt.Println(<-out)
    }

    // The `range` inside the goroutine from sq() will exit,
    // and the goroutine will return.
    close(in)
}

If it just spat out an ever increasing set of squares, then done would be necessary inside an infinite loop.

func sq(done chan bool) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        n := 0
        for {
            select {
                case <-done:
                    return
                case out<-n*n:
                    n++
            }
        }
    }()
    return out
}

func main() {
    done := make(chan bool)
    out := sq(done)
    for range []int{1,2,3,4} {
        fmt.Println(<-out)
    }

    // The switch in the goroutine will be able to read
    // from done (out's buffer being already full) and return.
    done <- true
}
🌐
Blogger
golangtutorials.blogspot.com › 2011 › 06 › channels-in-go-range-and-select.html
GoLang Tutorials: Channels in Go - range and select
June 11, 2011 - But by using the range keyword on the channel in the code above, we avoid that need - now when the channel is closed, the for loop automatically stops. The select keyword when used in conjunction with many channels works like a are-you-ready polling mechanism across different channels.
Find elsewhere
🌐
Reddit
reddit.com › r/golang › question about ranging over channels
r/golang on Reddit: Question about ranging over channels
September 11, 2017 -

Hey all! I am writing a function that will read messages sent to a channel and used like this:

go readMessages(messenger)

Where messenger is the channel of string messages.

I am confused about the difference between this:

func readMessages(messenger <-chan string) {
	for {
		select {
		case msg := <-messenger:
			fmt.Println(msg)
		}
	}
}

and this:

func readMessages(messenger <-chan string) {
	for msg := range messenger {
		fmt.Println(msg)
	}
}

Are they equivalent? Or Should I prefer one over the other and why?

🌐
Go 101
go101.org › article › channel.html
Channels in Go -Go 101
The loop will try to iteratively receive the values sent to a channel, until the channel is closed and its value buffer queue becomes blank. With for-range syntax on arrays, slices and maps, multiple iteration variables are allowed.
🌐
Reddit
reddit.com › r/golang › question: multiple channels + select vs one channel of interface
r/golang on Reddit: Question: multiple channels + select vs one channel of interface
January 11, 2021 -

Suppose you have a long-running goroutine that accepts different operations. In my case, it is a routine that listens for different events and reacts to them. I am using a single coroutine because the events are not independent and the reactions depend on an internal state (and I can avoid locks) and it's a pretty short code to have it in a single function.

My question, I can create a channel for each operation and use select in the goroutine or use a single channel of a common interface and dispatch by message type.

The only advantage I see in the single channel approach is that messages are dispatched on the order they are received, which can be important on some scenarios, but in my case it is not.

Do you have any experience or opinion about which approach is better?

Thanks!

🌐
Golangmastery
golangmastery.github.io › courses › concurrent-programming-with-go › 12-range-over-channels
Range over Channels | Golang Mastery
This makes range particularly useful ... uses range to process jobs: ... When using range with select, you need a different approach since range is a loop and select is a block....
🌐
Go by Example
gobyexample.com › select
Go by Example: Select
Go’s select lets you wait on multiple channel operations. Combining goroutines and channels with select is a powerful feature of Go · For our example we’ll select across two channels
🌐
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
🌐
Go
go.dev › tour › concurrency › 5
select statement
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... The select statement lets a goroutine wait on multiple communication operations.
Top answer
1 of 3
22

1- When you are dealing with one channel, it is OK to use for,
consider this working code ( The Go Playground ):

package main

import "fmt"

func main() {
    ch := make(chan int, 2)
    ch <- 1
    ch <- 2
    close(ch)
    for range ch {
    }
    fmt.Println("Done.")
}

This will empty the channel.
Note: you should close the channel or you should use break statement to finish that loop.


2- When you are dealing with more channels you may use select, like this ( The Go Playground ):

for {
    select {
    case <-pause:
        fmt.Println("pause")
        select {
        case <-play:
            fmt.Println("play")
        case <-quit:
            wg.Done()
            return
        }
    case <-quit:
        wg.Done()
        return
    default:
        work()
    }
}

3- Using nil and closed channel ( The Go Playground ):

package main

import "fmt"

func main() {
    var quit chan struct{} // nil

    select {
    case <-quit:
        fmt.Println("1")
    default:
        fmt.Println("2") // This runs
    }

    quit = make(chan struct{}, 1)

    select {
    case <-quit:
        fmt.Println("10")
    default:
        fmt.Println("20") // This runs
    }

    quit <- struct{}{} // send

    select {
    case <-quit:
        fmt.Println("100") // This runs
    default:
        fmt.Println("200")
    }

    close(quit)

    select {
    case <-quit:
        fmt.Println("1000") // This runs
    default:
        fmt.Println("2000")
    }

    select {
    case <-quit:
        fmt.Println("10000") // This runs
    default:
        fmt.Println("20000")
    }
}

output:

2
20
100
1000
10000

Select statements

A "select" statement chooses which of a set of possible send or receive operations will proceed. It looks similar to a "switch" statement but with the cases all referring to communication operations.

A case with a RecvStmt may assign the result of a RecvExpr to one or two variables, which may be declared using a short variable declaration. The RecvExpr must be a (possibly parenthesized) receive operation. There can be at most one default case and it may appear anywhere in the list of cases.

Execution of a "select" statement proceeds in several steps:

For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon entering the "select" statement. The result is a set of channels to receive from or send to, and the corresponding values to send. Any side effects in that evaluation will occur irrespective of which (if any) communication operation is selected to proceed. Expressions on the left-hand side of a RecvStmt with a short variable declaration or assignment are not yet evaluated. If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the "select" statement blocks until at least one of the communications can proceed. Unless the selected case is the default case, the respective communication operation is executed. If the selected case is a RecvStmt with a short variable declaration or an assignment, the left-hand side expressions are evaluated and the received value (or values) are assigned. The statement list of the selected case is executed. Since communication on nil channels can never proceed, a select with only nil channels and no default case blocks forever.

2 of 3
5

In your case, it seems that a simple loop would suffice:

for _ = range ch {
    fmt.Println("drain")
}

Select is required, if you have multiple goroutines to handle. The tour has an example of range and close.

Another case for select would be idempotent channel closes: https://play.golang.org/p/_Ol42BvuuS.

🌐
Better Programming
betterprogramming.pub › 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 - The consumer goroutine doesn’t have to coexist with the producer goroutine to receive the values — i.e. even if the producer goroutine finishes (and closes the channel), the consumer goroutine range loop will receive all the values.
🌐
Psj
psj.codes › concurrency-in-go-part-1-goroutines-channels-and-select
Concurrency in Go (Part-1): Goroutines, Channels and Select
August 28, 2023 - The important thing to notice is ... otherwise for loop will keep on waiting forever. In Go, the select statement is like a switch statement but for channels....
🌐
Dave Cheney
dave.cheney.net › 2013 › 04 › 30 › curious-channels
Curious Channels | Dave Cheney
Being able to detect if your channel is closed is a useful property, it is used in the range over channel idiom to exit the loop once a channel has been drained. package main import "fmt" func main() { ch := make(chan bool, 2) ch <- true ch <- true close(ch) for v := range ch { fmt.Println(v) // called twice } } but really comes into its own when combined with select.
🌐
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