Which Go standard-library calls actually stop early when the context.Context you passed them is cancelled?
answer
- Follow the parameter, not the goroutine
- No ctx argument, no reaction
- database/sql and net/http accept one
- os.File.Read and time.Sleep cannot
- Cancelling closes a channel, kills nothing
basics
~10 sOnly the calls you handed the context to. database/sql's QueryContext, http.NewRequestWithContext, net.Dialer.DialContext and exec.CommandContext abort. Anything with no ctx parameter, such as os.File.Read, io.Copy or time.Sleep, runs to completion.
solid answer
~40 sCancellation reaches exactly as far as you threaded the context, and no further. The standard library honours it wherever an API takes a `ctx`: `database/sql` (`QueryContext`, `ExecContext`, `BeginTx`), `net/http` on both sides (`http.NewRequestWithContext` aborts the dial, the round trip and body reads; on the server `r.Context()` is cancelled when the client disconnects), `net.Dialer.DialContext`, `os/exec` via `exec.CommandContext`, and `sql.Conn`/`Rows` operations derived from those. Everything else is deaf to it: `os.File.Read` and `os.File.Write`, a bare `net.Conn.Read`, `io.Copy` over them, `json.Decoder.Decode` reading from such a stream, `time.Sleep`, and any third-party function whose signature has no context. There is also no way to kill a goroutine from outside, so a pure CPU loop keeps burning until it checks `ctx.Err()` itself. Cancelling is a signal that reaches watchers, not a stop button on the runtime.
go deeper
Be ready to say that a context only reaches functions you passed it to, and to name two that take one (database/sql QueryContext, http.NewRequestWithContext) and two that do not (os.File.Read, time.Sleep).
Explain the mechanism: Done is a channel that gets closed, and each API that accepts a context selects on it or hands it to a driver. Show where a chain drops the context and cancellation therefore stops.
Demonstrate the audit habit. Given a stuck job, walk the call chain and identify the first layer with no context parameter, then say what you would change so the operator's cancel actually lands.
Frame it as an API rule for the codebase: any exported function that can block on I/O takes a context.Context as its first parameter, and code review rejects the ones that do not, because a missing parameter is an un-cancellable layer forever.
## The one rule A `context.Context` carries a cancellation signal: `ctx.Done()` is a channel that is closed when the context is cancelled or its deadline passes, and `ctx.Err()` then returns `context.Canceled` or `context.DeadlineExceeded`. Nothing else happens. The runtime does not walk your goroutines, does not close your file descriptors, and cannot interrupt a system call that is already in flight. **Work stops early only where some code is watching that channel** - and the only code that can watch it is code you gave the context to. So the practical question at review time is never "is this cancellable?" but "does this call take a `ctx`, and did I pass mine?" ## Calls in the standard library that honour it - **`database/sql`** - `DB.QueryContext`, `DB.ExecContext`, `DB.QueryRowContext`, `DB.BeginTx`, `Conn.PingContext`, `Stmt.ExecContext`. The pool passes the context to the driver, which sends a cancel to the server for an in-flight statement. A query that is already running comes back with `context.Canceled` rather than a result. You still have to `Close` the `Rows` yourself. - **`net/http`, client side** - `http.NewRequestWithContext(ctx, method, url, body)`. The context covers the whole exchange: DNS and dial, TLS handshake, writing the request, waiting for headers, and every `Read` on `resp.Body`. Build the same request with `http.NewRequest` and it silently carries `context.Background()`, so your cancellation does nothing. - **`net/http`, server side** - `r.Context()` is cancelled when the client goes away or when the handler returns. Anything you launched with that context stops with it. - **`net.Dialer.DialContext`** and the `net.Resolver` lookups that take a context - these bound the connect and the DNS step. - **`os/exec`** - `exec.CommandContext` kills the child process when the context is done. ## Calls that cannot honour it `os.File.Read`, `os.File.Write`, `net.Conn.Read` and `net.Conn.Write` have no context parameter, and adding one would not help: the goroutine is parked inside a blocking read. `io.Copy`, `bufio.Scanner.Scan`, `csv.Reader.Read` and `json.Decoder.Decode` are all built on top of those, so they inherit the deafness - a decoder halfway through a slow stream will not stop because you cancelled something. `time.Sleep` has no context either (that is what a `select` on `ctx.Done()` and a `time.After` case is for). And a loop doing arithmetic is the purest case: Go deliberately has no `goroutine.Kill`, so the loop runs until it chooses to look. ## What this means in a batch job Picture a job that walks a directory tree of large input files, reads each one, transforms it, and writes results to a database. Cancel it, and three different things happen at three layers. The database writes stop almost immediately, because `ExecContext` was given the context. The outbound HTTP call for enrichment stops, because the request was built with `NewRequestWithContext`. The `os.File.Read` chewing through a 4 GB input on a slow network mount does not stop at all - the job appears to hang, and an operator watching open descriptors with `lsof` sees the count stay flat instead of falling. The fix is not more contexts; it is code in the file loop that checks between reads. ## The mental model to carry into an interview Draw the call chain and mark every edge that takes a `ctx`. The cancellation signal flows down those edges only. Wherever an edge drops the context - a helper that takes no context, a request built without one, a goroutine started with `context.Background()` - cancellation stops there, and everything below it keeps running. That is also the review heuristic: a function that does I/O and takes no `context.Context` is a function that cannot be cancelled, and if it can block for a long time that is a defect worth raising. One more consequence worth stating plainly: cancellation is not cleanup. Cancelling a context does not close files, does not roll back transactions, does not close `sql.Rows`, and does not delete temporary output. Those are still your `defer`s to write.
- How does cancellation reach an outbound HTTP request?Through `http.NewRequestWithContext`, which attaches the context to the request. The transport then aborts the dial, the round trip and any `resp.Body.Read` when Done closes, returning the context error. A request built with `http.NewRequest` carries `context.Background()` instead, so cancelling yours has no effect on it.
- What happens to an in-flight database/sql query when its context is cancelled?`database/sql` asks the driver to cancel the statement, the call returns `context.Canceled`, and iteration over `Rows` stops. Because the connection may be left mid-protocol, the driver often closes it rather than returning a dirty connection to the pool. You still call `Rows.Close` and check `Rows.Err` yourself.
- Does cancelling a context stop a goroutine that is only doing computation?No. Go gives you no way to kill a goroutine from outside; there is no preemptive abort you can trigger. The loop keeps running until it checks `ctx.Err()` or selects on `ctx.Done()` itself. If the work is long, break it into chunks and check between them.
A cancelled context is a memo circulated to everyone on the distribution list. People not on the list never see it and keep working.
saying these in an interview costs you the question
- Says cancelling a context kills the goroutine
- Assumes every standard-library call watches for cancellation
- Builds requests with http.NewRequest and expects cancellation to reach them
- Thinks os.File.Read returns context.Canceled
- Believes cancellation closes files and rolls back transactions for you