Concurrent goroutines often need to hand off results without sharing mutable state. A channel is a typed, synchronized conduit for that handoff: one goroutine sends a value and another receives it. The exchange coordinates their work without requiring a mutex around the transferred value. Channels carry data; context supplies the separate signal for cancellation and deadlines. section 1.4.2 section 1.4.3
The channel’s type and lifecycle make the handoff explicit:
- Create a channel with make(chan T). Send with ch <- value and receive with <-ch; a loop using range ch receives values until the channel closes. section 9.4
- Use chan<- T for a send-only parameter or <-chan T for a receive-only parameter when a function needs just one side of the exchange. section 9.4
- Give a channel a capacity with make(chan T, n) when buffering fits the data flow; close it when sending is finished so receivers can exit. section 9.4 section 9.6.1
- Use select to receive from multiple channels, such as separate feed and error streams. section 9.4
Fetching and saving RSS articles
An RSS fetcher can send each parsed article on an article channel while a saving goroutine ranges over that channel and adds articles to a store. A channel field can likewise carry feed URLs into an application’s processing loop. These handoffs separate fetching, routing, and storage without making every stage share the same working data. section 9.4
When an HTTP handler needs a complete response, fan out one fetch goroutine per feed and send each result or error to a shared results channel. A WaitGroup tracks completion; after all fetchers finish, close the channel. The handler ranges over it to combine articles and report errors. Keep the goroutine count manageable, use context for timeouts, and use a mutex when data really must be shared. section 9.5.2 section 9.6.1
Listing 9.23 main.go: New handler for fan-out/fan-in call
func (a *App) syncHandler(w http.ResponseWriter, r *http.Request) {
// Fan-out: launch goroutines for each feed
type result struct {
articles []*gofeed.Item
err error
}
resultsChan := make(chan result, len(feeds)) #1
var wg sync.WaitGroup #2
for _, feedURL := range feeds {
wg.Add(1)
go func(url string) {
defer wg.Done()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
fetchedArticles, err := a.fetchFeed(ctx, url)
resultsChan <- result{articles: fetchedArticles, err: err} #3
}(feedURL)
}
// Close results channel after all goroutines complete
go func() {
wg.Wait()
close(resultsChan)
}() #4
The /sync handler shows the concrete boundary between concurrent fetching and result collection: goroutines send typed results, while a WaitGroup governs when the results channel can close. section 9.5.2









