The context package is the standard for managing the lifecycle (cancellation, timeout) and passing metadata between parts of the code, especially when working with goroutines and external calls (HTTP, DB).
Creation:
It's important to cancel the context to properly free up resources. Example:
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() // ctx is then used in goroutine/HTTP request
The standard pattern is to pass the context as the first argument of functions, example:
func process(ctx context.Context) error { ... }
When can you pass context.TODO() and context.Background() instead of a real context, and what is the difference between them?
Many use context.Background() as a placeholder. However:
context.Background() is the root of the tree, use it only in main() and tests when initializing the "context tree" itself.context.TODO() is needed when it's unclear what the context should be — for temporarily "plugging gaps" during refactoring. In production code, TODO is unacceptable: you need to know exactly what is being passed and where.Story
In a microservice, they forgot to call cancel() after creating context.WithTimeout() — requests were stuck, finished goroutines left "hanging" timers in the runtime, leading to memory leaks.
Story
Passing context.Background() instead of the context from the request in handlers lost the entire cancellation chain and trace-id, causing timeout constraints not to work and request cancellations to fail.
Story
Through context.WithValue(), developers passed data, but the keys were strings. As a result, due to possible key collisions from different packages, unexpected errors occurred ("key already occupied"). Proper practice: use unique types for keys.