Level 7 /

Context

The rail system is getting complex: multiple trains, multiple signals, multiple routes. From the control tower, Gopher can see every signal on the line. The dispatch system sends instructions and cancellations flowing through the entire network, carrying the authority to stop everything if needed.

Gopher says

Context is how Go passes cancellation signals, deadlines, and request-scoped data through a chain of function calls. It’s the invisible thread connecting every layer of your application.

In baton-junction, every function takes ctx context.Context as its first parameter. If the SDK cancels a sync, that signal propagates through the connector, the client, all the way to the HTTP request.

Why Context Exists

Go supports concurrent operations via goroutines. When multiple operations run at once, you need a way to tell them to stop. If a user disconnects mid-request, the remaining API calls are pointless.

context.Context carries three things through a call chain:

  1. Cancellation signals - “stop, nobody needs the result”
  2. Deadlines - “give up after 30 seconds”
  3. Request-scoped data - loggers, trace IDs, auth info

Context as the First Parameter

When every function takes context in the same place, you never have to guess where to pass it. The convention exists so tooling, linters, and other developers can rely on consistent behavior across the entire ecosystem.

Go convention: context is always first, always named ctx:

keywordalways first parameteralways this type
func (c *Client) GetUsers(ctx context.Context, cursor string) ([]User, string, error)

This convention is universal across the Go ecosystem - every library, every framework.

Context Flowing Through the Baton App

Why does this flow matter? If the SDK cancels a sync, that signal must reach the HTTP request so it stops immediately. No wasted API calls, no orphaned goroutines. The same chain also carries request-scoped data like loggers for tracing.

Context starts at main() and flows unbroken through every layer:

If the SDK cancels the context, the HTTP request is aborted immediately. No wasted API calls.

Creating a Root Context

Every context chain has to start somewhere. You need a root context when your program begins (in main()) or when writing tests that don’t have a real caller. Everywhere else, you receive context from above.

Two functions create top-level contexts:

ctx := context.Background()   // the root  -  used in main() and tests
ctx := context.TODO()         // same thing, but signals "I need a real context later"

Every other function receives context from its caller. Only main() and tests create new ones.

The Receive-Pass Pattern

Here’s the good news: working with context is mostly plumbing. You rarely create or cancel it yourself. Your job as a connector author is simple:

func (o *userBuilder) List(ctx context.Context, ...) (...) {
    l := ctxzap.Extract(ctx)                    // 1. optionally extract logger
    users, _, err := o.client.GetUsers(ctx, cursor)  // 2. pass ctx along
    // ...
}

Receive context. Optionally extract data from it (like a logger). Pass it to every function you call. Never ignore it, never replace it.

The Underscore Pattern

Go’s compiler requires you to use every declared variable. The blank identifier _ is how you tell it “I’m intentionally ignoring this.” It shows up in three places:

func (o *userBuilder) ResourceType(_ context.Context) *v2.ResourceType { ... }

for _, u := range users { ... }

_, err := client.Delete(ctx, id)

The first discards a parameter the interface requires but you don’t need. The second discards the index in a range loop. The third discards the result of a function when you only care about the error. You’ll see all three forms throughout baton-junction.

Quick Check

What function creates the root context in main()?

Exercise: Thread Context Through

Write three functions that pass context through a chain. FetchUser should call GetFromAPI with the user path. GetFromAPI should call DoRequest. DoRequest should format and return the method and path. Every function receives context as its first parameter and passes it to the next. All three return a string and an error.

Gopher says

Context seems like ceremony at first, but it’s the thread that connects your entire application. Receive it, pass it along, and the framework handles cancellation and timeouts.

In baton-junction, context flows from main.go through the connector, into each builder, down to the HTTP client - if the SDK cancels, every in-flight API request aborts immediately.

Next: the standard library!

Explore the related connector code files

Next Lesson

Standard Library

Continue