If you have used the context package in the standard library, you might have noticed that there
are two constructors that create an empty context: context.Background()
and context.TODO()
.
So which one should you use when you need a new context?
Let’s check the docs
First, context.Background()
:
Background returns a non-nil, empty Context. It is never canceled, has no values, and has no deadline. It is typically used by the main function, initialization, and tests, and as the top-level Context for incoming requests.
Then, context.TODO()
:
TODO returns a non-nil, empty Context. Code should use context.TODO when it’s unclear which Context to use or it is not yet available (because the surrounding function has not yet been extended to accept a Context parameter).
So both return a “non-nil, Empty Context” and should function the same from a technical perspective. However, they differ in their intent and what they communicate to the people reading your code.
Functional reasons
If your code requires an empty context for functional reasons, you should use context.Background()
. It is the “standard” way of creating an empty context.
Like the docs said, common cases where you might want to use an empty context are in your main
function or in (unit) tests. context.Background()
is the right function to use in these cases.
Work in progress
I don’t think it’s a coincidence that it is spelled the same as a typical TODO
comment. You should use context.TODO()
as a “marker” for code that should to be fixed at some point.
The situation described in the documentation is quite common: You want to call a function that requires a context as input, but the function you’re calling it from does not accept a context yet.
Let’s look at an example.
package main
import "context"
func main() {
a()
}
func a() {
b(context.TODO())
}
func b(ctx context.Context) {
// ...
}
Function a
calls function b
, but function a
has not been extended to accept a context yet. So function a
calls b
with a context.TODO()
context.
This signals to other developers that there is work to be done here. You might not always want to do this work immediately, because extending a function can require a bit of care:
- If the extended function is an exposed function (begins with a capital letter), adding a parameter would break all users of that function.
- If the callers of the extended function don’t accept contexts yet, they in turn need to be extended as well. Leading to cascade of changes.
Never pass a nil context
Choosing the wrong constructor will not lead to your application blowing up. Both context.Background()
and context.TODO()
function in the same way.
However there is one thing that would result in issues: passing nil
as a context. Even the docs warn against it:
Do not pass a nil Context, even if a function permits it. Pass context.TODO if you are unsure about which Context to use.
It can be a bit nefarious, becase the compiler will not warn you if you do provide nil
as a context.
Eventually this will result in panics, for example, when you try to derive a new context:
package main
import "context"
func main() {
context.WithCancel(nil)
}
Which will result in:
panic: cannot create context from nil parent
Or when you attempt to call a method on the nil
context itself:
package main
import "context"
func main() {
var ctx context.Context = nil
ctx.Err()
}
Resulting in:
panic: runtime error: invalid memory address or nil pointer dereference
So never pass a nil
context, unless you like tracing down these kind of panics.
Outro
As you just saw, the only way things can really go wrong is by providing a nil
context, so if you use context.Background()
or context.TODO()
you will be fine. If you have any questions or comments feel free to reach out to me.
And, if you want to read more posts like this, sign up for my newsletter below and receive the latest posts in your inbox.
Get my free newsletter every second week
Used by 500+ developers to boost their Go skills.
"I'll share tips, interesting links and new content. You'll also get a brief guide to time for developers for free."