The Hierarchial Nature of Go Context

Learn why the context package in Go is so powerful

Gopher looking at context

Context is one of the most powerful constructs in Go, and this power originates from its hierarchical nature. As you know, whenever you create a new context, you always have to pass in a parent context. For example,

context.WithValue(parent, "foo", "bar")
context.WithTimeout(parent, time.Minute)

This means that whenever a parent context expires, either due to timeout or due to any other event, the children that inherited the parent are also canceled. However, this is not true the other way around, meaning that when a child context expires, the parent remains unaffected.


package main

import (

func main() {
	ctxA, aCancel := context.WithCancel(context.Background())
	defer aCancel()

	ctxB, bCancel := context.WithCancel(ctxA)
	defer bCancel()

	ctxC, cCancel := context.WithCancel(ctxB)
	defer cCancel()

	var wg sync.WaitGroup

	go func() {
		log.Println("A context done")

	go func() {
		log.Println("B context done")

	go func() {
		log.Println("C context done")



In the example above, A is a parent to B, and B is a child to C, making A also a parent (or grandparent) to C.

|___ B

As a result, the cancellation of A also results in the cancellation of B and C.

So, how was this useful to me?

A year ago, I was working on a project that spawned multiple goroutines, and those goroutines in turn would spawn another set of goroutines. The children goroutines were tied to their parents, and their parents were tied to their parent. Therefore, if any of the parents failed during runtime, I wanted only its children to exit without affecting other goroutines. Then, the parent would restart its children.

Using context, it was as simple as calling cancel() in the parent on the context passed to the children. This resulted in the cancellation of the child goroutines and their subsequent exit. After this, the concerned parent would re-spawn new children.

Mind Blowing