Context key types

May 30, 2025

So we’ve established that context keys should be of an unexported type in your package. That still leaves a lot of options. Which type is ideal? Today we’ll look at some options, and their pros and cons.

Before looking at specific types, let’s consider what sorts of things we want from such a type.

  1. It must be comparable, or it won’t work as a key. This means we can’t use function or channel types, for example. These wouldn’t make for very natural keys anyway, so not likely much of a practical constraint.

  2. It should probably use the least amount of memory necessary.

  3. It should be readable, both in code, and when debugging (i.e. in logging or debugger output)

  4. It should be easy to declare multiple keys in the same package, without conflict.

  5. Optionally, it may be nice if it can be expressed as a const.

With these goals in mind, let’s now consider some possible types, starting with the empty struct for today:

struct{}

This is one option that many people won’t even consider off the bat, because it’s a bit obscure. But let’s talk about it. Does it meet our criteria?

It is comparable. It uses 0 memory, so it’s ideal for criteria #2, and in fact this is what makes it an attractive option. But it fails on all the other counts.

For an empty struct to work as a context key, you must define a new, distinct type for every key.

type keyOne struct{}
type keyTwo struct{}

ctx := context.WithValue(context.Background(), keyOne{}, "value1")
ctx = context.WithValue(ctx, keyTwo{}, "value2")

It’s not sufficient to do this:

type key struct{}

var keyOne = struct{}
var keyTwo = struct{}

or even this:

var keyOne = &struct{}
var keyTwo = &struct{}

Why not?

From the Go spec:

Pointers to distinct zero-size variables may or may not be equal.

So to make use of the empty struct (or any other zero-sized type) for context keys, we must rely on type information, and thus one type per key. This is cumbersome. And it makes debugging less straight forward.

Bottom line: Probably don’t use this option.


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


Context key type: Final recommendation

First, a correction! An astute reader pointed out that I made a small mistake in my post on June, 10, with regard to string context keys. My code example showed: type contextKey string const ( KeyUserID = "user_id" KeyTransactionID = "transaction_id" KeyMessageID = "message_id" ) But it should have been: type contextKey string const ( KeyUserID contextKey = "user_id" KeyTransactionID contextKey = "transaction_id" KeyMessageID contextKey = "message_id" ) This is a nasty kind of bug, because the code will continue to work as expected—just without any protection from key collisions!


Struct context keys

First off, an apology for being rather unreliable with my “daily” emails. I’m in the middle of some traveling (was in the UK and Netherlands last week, and will be on a US road trip starting tomorrow), so I’m writing when I have a spare moment, which isn’t that often. I’ve been talking about finding the ideal type for a context key. So far I’ve looked at empty structs (e.g. struct{}), integers, and strings.


String context keys

By now we’ve looked at empty structs (struct{}), and integers as possible context key types. Today, let’s consider string context keys. type contextKey string const ( KeyUserID contextKey = "user_id" KeyTransactionID contextKey = "transaction_id" KeyMessageID contextKey = "message_id" ) How does this stack up against our 5 criteria? Must be comparable. ✅ Check. Use minimal memory. ✅ Using a string will typically use a bit more memory than an integer (typically 32 bytes vs 16), but still quite minimal.

Get daily content like this in your inbox!

Subscribe