Skip to content
Go Basics: Finish a Channel Stream Without Sentinel Values

Go Basics: Finish a Channel Stream Without Sentinel Values

October 31, 2026

A data stream should be able to contain zero without treating zero as an end marker. Close the channel after sending the final value, and let the receiver distinguish data from completion.

Prepare a scratch directory

Tested on Linux amd64 with Go 1.26.10. Install Go and make go available on PATH. You also need Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. The example uses only the standard library and needs no module or external dependency.

Keep the same terminal open. Stop if setup fails, before saving the source:

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

Run the example

Save this as channel-close-demo.go:

package main

import "fmt"

func produce() <-chan int {
	values := make(chan int, 3)
	go func() {
		defer close(values)
		for _, n := range []int{0, 2, 4} {
			values <- n
		}
	}()
	return values
}
func main() {
	values := produce()
	sum := 0
	for n := range values {
		fmt.Println("Value:", n)
		sum += n
	}
	n, open := <-values
	fmt.Printf("After drain: value=%d open=%t\n", n, open)
	fmt.Println("Sum:", sum)
}

Run it:

go run channel-close-demo.go

Observed output:

Value: 0
Value: 2
Value: 4
After drain: value=0 open=false
Sum: 6

Read the result

Range receives all values and stops only after the channel is closed and drained. A later receive returns the element type’s zero value with a false second result. The receive-only return type prevents callers from sending or closing through that variable.

Apply it carefully

The sender that owns completion should close the channel. Sending on a closed channel or closing it twice panics. If a consumer may stop early, add a cancellation path to the producer so it cannot remain blocked sending. Buffering alone is not a general cancellation strategy.

The official Go reference documents the behavior used here.

Clean up

Remove the saved source, return to the starting directory, and remove the empty practice directory. go run does not leave an executable here; Go manages its build cache separately.

rm -- channel-close-demo.go
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

If the directory is not empty, inspect its remaining contents before removing anything else.

Acknowledgements

This post was created with assistance from AI.

Last updated on