Golang unbuffered channel - Correct Usage
I've majorly written the review comments inline with the code. I could've made the code simple as @thwd suggested; but as you're learning and this is not an actual use case, I'm not changing the logic.
Also, to know about buffered channels better, do read this.
package main

import (
    &quot;fmt&quot;
    &quot;sync&quot;
)

// As goroutine could schedule in any order, it's better if the
// operation is stored as well, with the result.
type result struct {
    op string
    v  int
}

// It's better to use unidirectional channels if bi-directional
// is not needed. It can also distinguish how a channel should be used
// in different function/ methods (eg. producer, consumer)
type fn func(int, int, chan&lt;- result, *sync.WaitGroup)

func add(x, y int, r chan&lt;- result, wg *sync.WaitGroup) {
    fmt.Println(&quot;op: add&quot;)
    r &lt;- result{&quot;add&quot;, x + y}
    wg.Done()
}

func sub(x, y int, r chan&lt;- result, wg *sync.WaitGroup) {
    fmt.Println(&quot;op: sub&quot;)
    r &lt;- result{&quot;sub&quot;, x - y}
    wg.Done()
}

func prod(x, y int, r chan&lt;- result, wg *sync.WaitGroup) {
    fmt.Println(&quot;op: prod&quot;)
    r &lt;- result{&quot;prod&quot;, x * y}
    wg.Done()
}

func main() {
    // In case of multiple declaration, you can group
    // But it's totally dependent on the developer writing it.
    // But the ultimate goal is write clean code.
    var (
        fops = []fn{add, sub, prod}
        ch   = make(chan result, len(fops))
        wg   sync.WaitGroup
        x, y = 10, 20
    )

    wg.Add(len(fops))
    for _, f := range fops {
        go f(x, y, ch, &amp;wg)
    }

    wg.Wait()
    close(ch)

    // Either use range, or you run a loop from [0, len(fops));
    // that would work as well!
    for v := range ch {
        fmt.Println(v)
    }
}