>_
GolangStepByStep
Intern

Channels

Communicate between goroutines safely using typed channels

# Why Channels Matter

Channels are the coordination backbone for concurrent Go programs: they move data, synchronize execution, and model ownership transfer between goroutines.

In production systems, channels usually power worker pools, streaming pipelines, fan-out/fan-in processing, and cancellation-aware background work.

# Unbuffered vs Buffered Channels

Unbuffered channels are a rendezvous point: send blocks until a receiver is ready. Buffered channels allow temporary decoupling, but still block when full.

syncCh := make(chan int)      // unbuffered
queueCh := make(chan int, 3)   // buffered

// syncCh send waits for receiver
// queueCh send waits only when buffer is full

# Sending, Receiving, and Closing

Only the sender should close a channel. Receivers use comma-ok or range loops to detect completion.

v, ok := <-ch
if !ok {
    fmt.Println("channel closed")
}

for v := range ch {
    fmt.Println(v)
}

# select, Timeouts, and Non-Blocking Ops

select waits on multiple channel operations, enables timeout behavior, and supports non-blocking checks via default.

select {
case v := <-ch:
    fmt.Println("got", v)
case <-time.After(100 * time.Millisecond):
    fmt.Println("timeout")
default:
    fmt.Println("nothing ready right now")
}

# Directional Channels and API Design

Directional channels document intent and prevent misuse at compile time. Use chan<- T for producers and <-chan T for consumers.

func producer(out chan<- int) { /* send only */ }
func consumer(in <-chan int) { /* receive only */ }

# Pipeline and Cancellation Pattern

In multi-stage pipelines, combine channels with a done signal so goroutines can exit early instead of leaking when downstream stops consuming.

func square(done <-chan struct{}, in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for n := range in {
            select {
            case out <- n * n:
            case <-done:
                return
            }
        }
    }()
    return out
}

# Practice Challenge

Build a worker pool with 3 workers consuming jobs and producing results; then add cancellation support so workers can stop cleanly when the caller no longer needs results.

package main

import "fmt"

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        results <- j * j
    }
}

⚡ Key Takeaways

  • Use unbuffered channels for strict handoff, buffered channels for burst absorption
  • Only senders should close channels; receivers detect closure via comma-ok or range
  • Use select for timeouts, cancellation, and multiplexing
  • Use directional channel types to enforce API intent
  • Add cancellation paths in pipelines to avoid goroutine leaks
practice & review