I think the rule is: don't use a channel unless at some point you need a select with 2+ branches. The context.Done is a case in point. You're not just waiting around for a context to finish. You do two things: try some network request and wait to see if the context cancels before it finishes. Because it's made to work in a select block like that, there wasn't another, better thing to use than a channel for context.Done. But for just iterating through some stream, you take a context in and return an iterator object that will do all the channel stuff internally.