Golang unbuffered channel - Correct Usage Here is an example where I'm trying to understand the concepts of buffered channels&gt; I have three functions and create a buffered channel of length 3. And also passed a waitgroup to nofiy when all the goroutines are done. And finally collecting the values through range.
Could you please help me in reviewing this code? And where I could improve?
package main

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

type f func(int, int, chan int, *sync.WaitGroup)

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

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

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

func main() {
    var operations []f = []f{add, sub, prod}
    ch := make(chan int, len(operations))
    wg := sync.WaitGroup{}

    x, y := 10, 20

    wg.Add(len(operations))
    for _, i := range operations {
        go i(x, y, ch, &amp;wg)
    }
    wg.Wait()
    close(ch)
    for val := range ch {
        fmt.Println(val)
    }

}
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)
    }
}