What belongs in context values and what doesn't?
Question 270MediumGo 1.22 to 1.25
The official guidance: use context values only for request-scoped data that crosses process and API boundaries, not for passing optional parameters to functions.
Good fits: request/trace/span IDs, authenticated principal or tenant, deadline-related metadata, a request-scoped logger with fields already attached, and locale. These are cross-cutting data that middle layers pass along without looking at.
Bad fits:
- Dependencies such as DB handles, config, or HTTP clients. These belong in struct fields or constructor parameters, where the compiler checks them.
- Function parameters that affect business logic ("pageSize", "userID to delete"). Hiding them in context makes the API lie about what it needs, and a missing value turns a compile error into a runtime
nil. - Mutable shared state, and anything large.
// Bad: hidden, untyped dependency
func DeleteUser(ctx context.Context) error {
db := ctx.Value("db").(*sql.DB) // panics if missing
id := ctx.Value("userID").(int64)
_, err := db.ExecContext(ctx, "DELETE FROM users WHERE id=$1", id)
return err
}
// Good: explicit
func (s *Store) DeleteUser(ctx context.Context, id int64) error {
_, err := s.db.ExecContext(ctx, "DELETE FROM users WHERE id=$1", id)
return err
}
What the interviewer is looking for: a simple rule of thumb. If the function cannot work correctly without the value, it should be a parameter.
More on Context
- Q268What does this print? (context value keys)
- Q269How is Value lookup implemented, and what are its performance implications?
- Q271Why is storing a context.Context in a struct considered an anti-pattern? Are there exceptions?
- Q272context.Background() vs context.TODO(): when do you use each, and why not pass nil?
- Q273What is context.AfterFunc and what are the semantics of its stop function?
- Q274How do you make a blocking call that doesn't accept a context (e.g. net.Conn.Read) cancelable?