Code can check ctx.Err() during a loop and stop when cancellation appears.

early stop Cancellation is cooperative: code checks the context and chooses when to stop.

Early Stop

stopAfter
early_stop.go
Replay: real traced execution (multi-file project)
package main

import (
	"context"
	"fmt"
)

func main() {
	var stopAfter = 2
	ctx, cancel := context.WithCancel(context.Background())
	items := []string{"red", "green", "blue", "gold"}

	processed := 0
	for _, item := range items {
		if ctx.Err() != nil {
			break
		}
		processed++
		fmt.Println("item=", item)
		if processed == stopAfter {
			cancel()
		}
	}

	fmt.Println("processed=", processed)
	fmt.Println("err=", ctx.Err())
}
package main

import (
	"context"
	"fmt"
)

func main() {
	var stopAfter = 3
	ctx, cancel := context.WithCancel(context.Background())
	items := []string{"red", "green", "blue", "gold"}

	processed := 0
	for _, item := range items {
		if ctx.Err() != nil {
			break
		}
		processed++
		fmt.Println("item=", item)
		if processed == stopAfter {
			cancel()
		}
	}

	fmt.Println("processed=", processed)
	fmt.Println("err=", ctx.Err())
}
package main

import (
	"context"
	"fmt"
)

func main() {
	var stopAfter = 4
	ctx, cancel := context.WithCancel(context.Background())
	items := []string{"red", "green", "blue", "gold"}

	processed := 0
	for _, item := range items {
		if ctx.Err() != nil {
			break
		}
		processed++
		fmt.Println("item=", item)
		if processed == stopAfter {
			cancel()
		}
	}

	fmt.Println("processed=", processed)
	fmt.Println("err=", ctx.Err())
}
  1. stopAfter ← 2, ctx ← &context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:interface {}(nil)}, children:map[context.canceler]struct {}(nil), err:error(nil), cause:error(nil)}

    8func main() {9  var stopAfter→ 2 = 2 //@stopAfter=3, 410  ctx→ &context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:interface {}(nil)}, children:map[context.canceler]struct {}(nil), err:error(nil), cause:error(nil)}, cancel→ (context.CancelFunc)(⟨addr A⟩) := context.WithCancel(context.Background())11  items→ []string{"red", "green", "blue", "gold"} := []string{"red", "green", "blue", "gold"}1213  processed→ 0 := 014  for _, item := range items {
  2. processed ← 1

    pass 1 of 3
    13processed := 014for _, item"red" := range items[]string{"red", "green", "blue", "gold"} {15  if ctx.Err() != nil {16    break17  }18  processed→ 1++19  fmt.Println("item=", item"red")20  if processed == stopAfter {
    outputitem= red
    All 3 passes — pass 1 is the card above
    passitemstopAfterctxprocessed
    1"red"0 1
    2"green"21 2
    3"blue"&context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:(chan struct {})(⟨addr B⟩)}, children:map[context.canceler]struct {}(nil), err:(*errors.errorString)(⟨addr C⟩), cause:(*errors.errorString)(⟨addr C⟩)}
  3. if processed == stopAfter

    19fmt.Println("item=", item)20if processed2 == stopAfter2 {21  cancel()22}
  4. if ctx.Err() != nil

    14for _, item := range items {15  if ctx&context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:(chan struct {})(⟨addr B⟩)}, children:map[context.canceler]struct {}(nil), err:(*errors.errorString)(⟨addr C⟩), cause:(*errors.errorString)(⟨addr C⟩)}.Err() != nil {16    break17  }
  5. fmt.Println("processed=", processed)

    25  fmt.Println("processed=", processed2)26  fmt.Println("err=", ctx&context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:(chan struct {})(⟨addr B⟩)}, children:map[context.canceler]struct {}(nil), err:(*errors.errorString)(⟨addr C⟩), cause:(*errors.errorString)(⟨addr C⟩)}.Err())27}
    outputprocessed= 2
    err= context canceled
  1. stopAfter ← 3, ctx ← &context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:interface {}(nil)}, children:map[context.canceler]struct {}(nil), err:error(nil), cause:error(nil)}

    8func main() {9  var stopAfter→ 3 = 310  ctx→ &context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:interface {}(nil)}, children:map[context.canceler]struct {}(nil), err:error(nil), cause:error(nil)}, cancel→ (context.CancelFunc)(⟨addr A⟩) := context.WithCancel(context.Background())11  items→ []string{"red", "green", "blue", "gold"} := []string{"red", "green", "blue", "gold"}1213  processed→ 0 := 014  for _, item := range items {
  2. processed ← 1

    pass 1 of 4
    13processed := 014for _, item"red" := range items[]string{"red", "green", "blue", "gold"} {15  if ctx.Err() != nil {16    break17  }18  processed→ 1++19  fmt.Println("item=", item"red")20  if processed == stopAfter {
    outputitem= red
    All 4 passes — pass 1 is the card above
    passitemstopAfterctxprocessed
    1"red"0 1
    2"green"1 2
    3"blue"32 3
    4"gold"&context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:(chan struct {})(⟨addr B⟩)}, children:map[context.canceler]struct {}(nil), err:(*errors.errorString)(⟨addr C⟩), cause:(*errors.errorString)(⟨addr C⟩)}
  3. if processed == stopAfter

    19fmt.Println("item=", item)20if processed3 == stopAfter3 {21  cancel()22}
  4. if ctx.Err() != nil

    14for _, item := range items {15  if ctx&context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:(chan struct {})(⟨addr B⟩)}, children:map[context.canceler]struct {}(nil), err:(*errors.errorString)(⟨addr C⟩), cause:(*errors.errorString)(⟨addr C⟩)}.Err() != nil {16    break17  }
  5. fmt.Println("processed=", processed)

    25  fmt.Println("processed=", processed3)26  fmt.Println("err=", ctx&context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:(chan struct {})(⟨addr B⟩)}, children:map[context.canceler]struct {}(nil), err:(*errors.errorString)(⟨addr C⟩), cause:(*errors.errorString)(⟨addr C⟩)}.Err())27}
    outputprocessed= 3
    err= context canceled
  1. stopAfter ← 4, ctx ← &context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:interface {}(nil)}, children:map[context.canceler]struct {}(nil), err:error(nil), cause:error(nil)}

    8func main() {9  var stopAfter→ 4 = 410  ctx→ &context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:interface {}(nil)}, children:map[context.canceler]struct {}(nil), err:error(nil), cause:error(nil)}, cancel→ (context.CancelFunc)(⟨addr A⟩) := context.WithCancel(context.Background())11  items→ []string{"red", "green", "blue", "gold"} := []string{"red", "green", "blue", "gold"}1213  processed→ 0 := 014  for _, item := range items {
  2. processed ← 1

    pass 1 of 4
    13processed := 014for _, item"red" := range items[]string{"red", "green", "blue", "gold"} {15  if ctx.Err() != nil {16    break17  }18  processed→ 1++19  fmt.Println("item=", item"red")20  if processed == stopAfter {
    outputitem= red
    All 4 passes — pass 1 is the card above
    passitemstopAfterprocessed
    1"red"0 1
    2"green"1 2
    3"blue"2 3
    4"gold"43 4
  3. if processed == stopAfter

    19fmt.Println("item=", item)20if processed4 == stopAfter4 {21  cancel()22}
  4. fmt.Println("processed=", processed)

    25  fmt.Println("processed=", processed4)26  fmt.Println("err=", ctx&context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:(chan struct {})(⟨addr B⟩)}, children:map[context.canceler]struct {}(nil), err:(*errors.errorString)(⟨addr C⟩), cause:(*errors.errorString)(⟨addr C⟩)}.Err())27}
    outputprocessed= 4
    err= context canceled