skip to content

Where do ctx context.Context and the error result belong in an exported Go function's signature?

level: juniorimportance: must knowfreq 74%

answer

  1. two positions are not yours to choose
  2. the standard library never varies here
  3. cancellation goes in, failure comes out
  4. ctx leads the parameters, error trails the results

basics

~20 s

A context.Context parameter goes first and is named ctx; the error result goes last. Everything else sits between them, with any variadic parameter at the end, because that is the shape the standard library and every reviewer expect.

solid answer

~50 s

Two orderings are fixed by convention across all Go code. If a function takes a `context.Context`, it is the first parameter and it is called `ctx` — never a struct field, never a trailing optional argument. If a function can fail, `error` is the last result, so calls read `v, err := f(ctx, x)` and the `if err != nil` check that follows lines up the same way in every package. The remaining parameters sit in between, ordered most-significant first, and a variadic parameter has to be last because the language requires it. Neither the ctx rule nor the error rule is enforced by the compiler; they are enforced by review, and the standard library is completely consistent about them — `http.NewRequestWithContext`, `exec.CommandContext`, `(*sql.DB).QueryContext`. Do not add a ctx parameter to a function that has nothing to cancel just to look modern.

code

go · 9 lines
go
// Conventional: ctx first, error last, variadic at the end.
func Evaluate(ctx context.Context, flag, env string, opts ...string) (bool, error) {
	return false, nil
}

// Unconventional: ctx buried in the middle, error first.
func EvaluateBad(flag string, ctx context.Context, env string) (error, bool) {
	return nil, false
}

go deeper

for a junior

Be ready to write a correct signature on the spot: ctx first and named ctx, error last. Know that these are conventions the whole standard library follows, not compiler rules.

for a middle

Explain what each position buys: a leading context makes cancellable calls visible and hard to skip, and a trailing error makes every call site read the same way. Know that only the variadic-last rule is enforced by the language.

for a senior

Show judgment about when a context belongs in the signature at all, and about parameter shape once a call grows past three or four arguments. An interviewer expects you to reject a speculative ctx that nothing honours.

for a principal

Own this as a written, mechanical rule in the team's style guide, so reviews argue about behaviour rather than argument order, and so generated and hand-written code in the codebase are interchangeable.

## The two rules An exported Go signature has two positions that are not yours to choose: 1. **If the function takes a `context.Context`, it is the first parameter, and it is named `ctx`.** 2. **If the function can fail, `error` is the last result.** Neither is checked by the compiler. Both are followed by essentially the whole standard library, so a Go reader treats a violation as a bug in the API rather than as a style preference. ``` func Evaluate(ctx context.Context, flag, env string) (bool, error) ``` ## Why ctx goes first A `context.Context` is not data the function operates on. It carries the deadline, the cancellation signal and any request-scoped values of the *call* — the ambient conditions under which the work runs. Putting it in a fixed, leading position means: - **A reader can tell at a glance which calls are cancellable.** Scanning a package's exported functions, every signature starting with `ctx context.Context` is one that may block, do I/O, or need a deadline. - **The parameter cannot be quietly skipped.** A trailing, "optional-looking" context invites callers to leave it out, and then a whole call tree becomes uncancellable. - **Tooling and generated code assume it.** Generated clients, middlewares and wrappers all produce and consume the leading-ctx shape. Two practical corollaries: never pass a nil context — use `context.TODO()` when you have not yet decided where the context comes from, and `context.Background()` at the top of a program or a test. And do not take a context at all if the function does no blocking work; adding one speculatively puts a parameter in the signature that promises cancellation you never implement. Name it `ctx`, not `c` and definitely not `context`, which would shadow the package name inside the body. ## Why error goes last Go functions return the answer first and the failure last: `(T, error)`, `(n int, err error)`, `(*Response, error)`. Every call site then has the same shape: ``` v, err := f(ctx, x) if err != nil { return ... } ``` If a package returned `(error, T)`, every one of its call sites would read backwards, and mechanical edits — adding a wrapper, wiring a retry, generating a mock — would all have to special-case it. The convention is worth more than any local argument for a different order. The contract that rides along with the position matters too: when the error is non-nil, callers are entitled to ignore the other results, so a function must never require the caller to read a value returned alongside a non-nil error. ## What sits in between - Required parameters, roughly most significant first — the subject of the call before its modifiers. - A variadic parameter (`opts ...Option`) must come last; that one *is* a compiler rule, not a convention, so a signature cannot put a context or anything else after it. - The receiver of a method is separate from all of this; a method still takes `ctx` as its first ordinary parameter. If a signature has grown past three or four parameters, or has two parameters of the same type where callers can transpose them, the fix is usually a struct parameter or an options type rather than more positions. ## Standard library evidence - `http.NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*http.Request, error)` - `(*sql.DB).QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)` - `exec.CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd` - `os.ReadFile(name string) ([]byte, error)` The first three show ctx leading even when a variadic follows; the last shows error trailing in a function that needs no context at all. ## In review This is one of the cheapest rules a team can write into a style guide, because it is mechanical: ctx first and named ctx, error last, variadic last, nothing else stipulated. A reviewer who sees `func Do(id string, ctx context.Context) (error, Result)` does not need to read the body to know the signature is wrong.

  • Where does a variadic options parameter go in a signature that also takes a context?
    The context stays first and the variadic must be last — that part is a language rule, not a convention, since only the final parameter may be variadic. So the shape is `f(ctx context.Context, required T, opts ...Option)`. This is also why a context can never be tacked on at the end of a signature that already has options.
  • Should a constructor like NewClient take a ctx as its first parameter?
    Only if construction itself blocks or can be cancelled — dialling a connection, fetching credentials, probing a health endpoint. If the constructor only assembles a struct, taking a context implies a cancellation the function never honours, and callers will reasonably expect passing a cancelled context to stop something.
  • Why is the parameter named ctx rather than context?
    Naming it `context` shadows the imported package inside the function body, so `context.WithTimeout` no longer resolves and the code has to be rewritten or the import aliased. `ctx` is universal in Go code, which also makes the leading-context shape easy to scan and to grep for.

saying these in an interview costs you the question

  • Puts error first because callers check it first
  • Adds ctx as a trailing parameter to avoid touching callers
  • Passes nil as a context instead of context.TODO
  • Claims the compiler enforces ctx-first and error-last
  • Names the parameter context, shadowing the package