In Go, the select statement allows a goroutine to wait on multiple channel operations. When combined with a default case, select can be used to perform non-blocking send and receive operations on channels.
📌 non-blocking /nɒn ˈblɒk.ɪŋ/ → does not wait; continues immediately
Normally, sending to or receiving from a channel may block if:
the channel is full (send)
the channel is empty (receive)
no goroutine is ready on the other side
Using select with default avoids blocking by trying once and moving on.
📌 block /blɒk/ → pause execution and wait
A non-blocking send attempts to send a value on a channel without waiting.
select {
case ch <- value: // send succeeded
default: // send would block, so skip
}
If the channel can accept the value:
the send happens immediately
If the channel cannot accept the value:
the default case runs
the goroutine continues execution
📌 attempt /əˈtempt/ → try to do something
📌 accept /əkˈsept/ → receive or take in
A non-blocking receive attempts to receive a value only if one is available.
select {
case value := <-ch: // receive succeeded
default: // no value available
}
If a value is available:
it is received immediately
If no value is available:
default executes
execution continues without waiting
📌 receive /rɪˈsiːv/ → get or take something
📌 available /əˈveɪ.lə.bəl/ → ready to be used
ch := make(chan int)
Send and receive must happen at the same time
Non-blocking send will fail if no receiver is ready
Used for synchronization
📌 synchronization /ˌsɪŋ.krə.naɪˈzeɪ.ʃən/ → coordinating actions to happen together
ch := make(chan int, 2)
Channel can hold values temporarily
Non-blocking send succeeds if buffer is not full
Non-blocking receive succeeds if buffer is not empty
📌 buffer /ˈbʌf.ər/ → temporary storage area
select {
case ch <- v:
default:
}
If the send fails, the value is silently dropped.
📌 silently /ˈsaɪ.lənt.li/ → without any warning or message
This is unsafe:
if len(ch) < cap(ch) {
ch <- v // race condition.
}
📌 race condition /reɪs kənˈdɪʃ.ən/ → bug caused by timing between goroutines
Always prefer select.
Use non-blocking send/receive when:
you want to avoid deadlocks
dropping data is acceptable
implementing polling or event loops
building soft real-time systems
📌 deadlock /ˈded.lɒk/ → program stops because goroutines wait on each other
📌 polling /ˈpəʊ.lɪŋ/ → checking repeatedly
select + default enables non-blocking behavior
Non-blocking send = try to send once, never wait
Non-blocking receive = try to receive once, never wait
Works best with buffered channels
Safe and idiomatic Go concurrency pattern