Part 1 , Part 2 , Part 3 , Part 4 , Part 5

Buffered Channels in Go are similar to unbuffered channels, but they allow a writer to write without blocking if the channel is not full, which can be useful for concurrent data processing where the reader needs to be decoupled. However, using buffered channels can consume memory even when the buffer is empty, so they should be used with care. Nevertheless, they can be more efficient than unbuffered channels if the necessary amount of memory is known beforehand.

Playground: https://go.dev/play/p/RhcdgNNPNmb

package main

import (
	"fmt"
	"sync"
)

func main() {
	iMax, jMax := 10, 10
	// create a buffered channel to store the products of two numbers
	// the channel has the exact capacity of the number of products
	// that will be written to it
	productChan := make(chan int, iMax*jMax)
	wg := sync.WaitGroup{}
	// use many goroutines to compute the product of two numbers
	for i := 0; i < iMax; i++ {
		for j := 0; j < jMax; j++ {
			copyI, copyJ := i, j
			wg.Add(1)
			go func() {
				defer wg.Done()
				// send the product to an unbuffered channel
				productChan <- copyI * copyJ
			}()
		}
	}

	// wait here and allow all the go routines to finish
	// their work and write to the buffered channel
	// this is only possible because the channel is buffered
	// and has the exact capacity of the number of products
	// that will be written to it
	wg.Wait()
	// close the channel to signal that no more products
	// will be written to it
	// the close signal will appended to the end of the channel
	// and the channel will stay open until all the products
	// are read from the channel
	close(productChan)

	// read the products from the channel
	// until the channel is closed
	for product := range productChan {
		fmt.Println(product)
	}
	fmt.Println("Done")
}