Looping through data sets with Go Channels

Repeatedly iterating through multiple sets of data together can be a tricky proposition, leading to nested loops or difficult to follow logic. Luckily, channels in Go can help decompose this problem into

Let’s say you’re setting up QA of your online store application, and want to generate some test orders. For the sake of simplicity, let’s assume that you can only order one product at a time. An order may look something like this:

type Order struct {
AccountID int
ProductID int

We have three Accounts and Five products.

| AccountID | Name |
| 1 | Alice |
| 3 | Bob |
| 7 | Carol |
| ProductID | Name | Price |
| 719 | Apple | 0.75 |
| 822 | Banana | 1.20 |
| 1024 | Cherry | 0.10 |
| 1179 | Damson | 0.80 |
| 2001 | Elderberry | 0.55 |

A function to generate 100 orders with rotating values of AccountID and ProductId may look like this:

Which produces orders of the form:

[{AccountID:1 ProductID:719},
{AccountID:3 ProductID:822},
{AccountID:7 ProductID:1024},
{AccountID:1 ProductID:1179},
{AccountID:3 ProductID:2001},
{AccountID:7 ProductID:719},

This is reasonable concise, but lines 9 and 10 are a bit tricky to follow and have a high risk of typos — like using len(accountIDs) on line 10 which would result in only a subset of the product IDs to be used. Additionally, if the IDs are provided from a stream rather than an up-front slice, there would be a need for additional logic to reset as needed.

We can refactor this to move the responsibility for looping through IDs to a goroutine:

This function iterates through the provided data forever, and since the write to the output channel blocks until read, values will only be created as required. The createOrders function can then accept channels as parameters:

This results in a simpler, easy to read createOrders function, which is also independent of the source of the input data. Goroutines are cheap, so use of channels doesn’t have to be limited to concurrency.