What does context.WithTimeout(ctx, 5*time.Second) return when ctx already has 200ms left?
answer
- the tree is evaluated as a whole
- shortening is free, lengthening is not
- the earliest instant in the chain governs
- a callee's number is a ceiling
- the 5 seconds never gets a timer
basics
~10 sA context that still expires in about 200 milliseconds. A derived context can only shorten its parent's deadline, never extend it, so the earliest deadline in the chain wins and Err() then reports context.DeadlineExceeded.
solid answer
~50 sYou get a context that expires in roughly 200 ms, not 5 seconds. `context.WithTimeout` and `context.WithDeadline` can only ever make the effective expiry **earlier**; when the requested instant is later than the parent's, the derived context is documented as behaving like `context.WithCancel(parent)` - it simply rides the parent's expiry. `ctx.Deadline()` on the child reports the parent's earlier instant, `Done()` closes when the parent's deadline fires, and `Err()` on the child is then `context.DeadlineExceeded`, propagated down from the parent. This is the earliest-deadline-wins rule, and it is what makes a request budget safe to hand to code you do not control: a helper deep in the stack cannot buy itself more time than the request has. The practical corollary is that a generous number written by a callee is a **cap**, not a floor - if you need to know how much time you actually have, read `ctx.Deadline()` and use `time.Until`.
code
go · 8 linesparent, cancelParent := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancelParent()
child, cancelChild := context.WithTimeout(parent, 5*time.Second)
defer cancelChild()
<-child.Done() // returns after about 200ms, not 5s
fmt.Println(child.Err()) // prints: context deadline exceededgo deeper
Remember the direction: a derived context can only expire sooner than the one it came from. If a parent has 200 ms left, nothing underneath it gets more.
Explain the mechanism - when the requested instant is later than the parent's, the derived context behaves like a plain cancellable child, arms no timer of its own, and inherits the parent's expiry and its DeadlineExceeded error.
Use the rule when reading unfamiliar code: treat a library's timeout constant as a ceiling, read ctx.Deadline() with time.Until before starting expensive work, and know that the error value alone cannot tell you whose deadline fired.
Decide the house convention for reserving margin - who shortens a budget to keep room for an error response or a cleanup path, and how much - so that timeouts across your services degrade into clean responses rather than dropped connections.
## The rule Contexts form a tree: every derived context has a parent, and cancelling a parent cancels every descendant. Deadlines ride that tree with one extra rule - **a child's deadline can only be earlier than its parent's, never later.** `context.WithDeadline` documents this directly: if the parent's deadline is already earlier than the requested one, the call is semantically equivalent to `context.WithCancel(parent)`. No timer is armed for the later instant, because it could never be the one that fires first. Since `context.WithTimeout(parent, d)` is `context.WithDeadline(parent, time.Now().Add(d))`, the same rule governs both. So, concretely, with a parent that has 200 ms left: - `child.Deadline()` reports the parent's instant, roughly 200 ms out, with `ok == true`. - `child.Done()` closes about 200 ms later. - `child.Err()` is then `context.DeadlineExceeded` - the parent expired, and expiry propagates down as that same error. - The 5-second number has no observable effect whatsoever. The reverse direction is unrestricted: deriving a *shorter* budget always works, and it is how you carve a request's remaining time into pieces. ## Why the language does it this way A deadline is a promise to the caller: "I will be finished, one way or the other, by this instant." If any function down the stack could extend it, that promise would be worth nothing. A single library that decided it deserved 30 seconds would silently overrun a handler that had 200 ms, hold its slot in the connection pool, and keep working long after the client that wanted the answer had disconnected. Because extension is impossible, a budget is safe to hand to code you did not write. Whatever an imported package does with the context you gave it, it cannot outlive your deadline. That asymmetry - shortening is free, lengthening is forbidden - is the single most useful property of context deadlines. ## Cancellation direction The tree is one-way in the other sense too. Cancelling or expiring a parent cancels all children. Cancelling a child does **not** touch its parent or its siblings - which is exactly why calling the child's `CancelFunc` in a `defer` is safe and required: it releases that child's timer and unlinks it from the parent without disturbing anything else. ## What this means in practice **1. A callee's timeout constant is a ceiling.** When you read `context.WithTimeout(ctx, 30*time.Second)` inside a client library, do not read it as "this call takes up to 30 seconds". Read it as "this call takes up to 30 seconds *or* whatever the caller had left, whichever is less". The number only bites when the caller supplied no deadline at all, or a longer one. **2. Ask, do not assume.** If your code needs to behave differently on a tight budget - shrink a batch, skip an optional enrichment, choose a cheaper query plan - read the remaining time rather than guessing: ```go if dl, ok := ctx.Deadline(); ok && time.Until(dl) < 50*time.Millisecond { // not worth starting } ``` The `ok` flag matters: false means nobody in the chain set a deadline, which is a genuinely different situation from "plenty of time left". **3. Reserving time for yourself is done by shortening.** A handler that must always return a rendered error rather than a dead connection derives a child a little earlier than its own deadline, does the downstream work under the child, and keeps the remainder of the parent's budget to write the response. That works precisely because shortening is allowed and unconditional. **4. Distinguishing whose deadline fired takes more than Err().** Both the parent's expiry and the child's produce `context.DeadlineExceeded` on the child. If you need to know which layer ran out - and during an incident review you usually do - compare `ctx.Deadline()` values, or record per-hop timings, rather than inspecting the error value. ## The misconception this corrects The intuitive model is that each derived context sets its own independent timer and the *last* one you wrote wins. It does not: the tree is evaluated as a whole and the earliest instant governs. A second, subtler version of the same error is believing that a child with a longer timeout at least *extends* the deadline for its own subtree. It does not do that either; there is no mechanism anywhere in the context package for adding time. If you genuinely need work to outlive the incoming request - a cleanup write, a metrics flush - the answer is a context that is deliberately detached from the request's chain, with its own budget, not a longer child. Detaching is an explicit act with explicit consequences, and it is nothing like a derived deadline.
- Does calling the child's cancel function in that example affect the parent?No. Cancellation propagates downward only. Calling the child's `CancelFunc` closes the child's `Done()` and unlinks it from the parent, releasing its timer; the parent and any siblings are untouched and keep running to their own deadline. That one-way direction is why `defer cancel()` on every derived context is always safe.
- How does a handler reserve time to write an error response instead of dying with the request?Derive a child that expires earlier than your own deadline - read `ctx.Deadline()`, subtract a margin, and pass the child to the downstream work. When the child expires you still hold the remaining parent budget, so you can render a proper error. This works because shortening a deadline is always permitted.
- Given only ctx.Err() == context.DeadlineExceeded, can you tell which layer's deadline fired?No. A parent's expiry propagates to every descendant as the same `context.DeadlineExceeded` value, so the error alone cannot tell you whether your own budget or an ancestor's ran out. Compare `ctx.Deadline()` against the one you derived, or record per-hop timings, if you need to attribute it.
saying these in an interview costs you the question
- Says the child now has five seconds
- Thinks the most recently derived deadline overrides earlier ones
- Believes a child can extend its parent's budget
- Expects Err() to be context.Canceled after a parent's deadline fires
- Assumes ctx.Deadline() reports the number the child asked for