In the unbuffered channels post final example countUp(counts chan int) will always send values to the channel while printOutput(counts chan int) will always receive values from the channel. We can make that explicit in the type of the channel. The countUp argument will be counts chan <- int to signal a channel that only receives values. and the printOut argument will be counts <- chan int to signal a channel that only produces value. Feel free to check the final implementation on Go Playground.

func main() {
	countChannel := make(chan int)
	var wg sync.WaitGroup
	wg.Add(2)

	go func() {
		defer wg.Done()
		printOutput(countChannel)
	}()

	go func() {
		defer close(countChannel)
		defer wg.Done()
		countUp(countChannel)
	}()

	wg.Wait()
}

func countUp(counts chan<- int) {
	for i := 0; i < 10; i++ {
		time.Sleep(100 * time.Millisecond)
		counts <- i
	}
}

func printOutput(counts <-chan int) {
	for i := range counts {
		fmt.Printf("%d ", i)
	}
}