skip to content

In Go, what is the difference between context.WithTimeout and context.WithDeadline?

level: juniorimportance: must knowfreq 72%

answer

  1. one is relative, one is absolute
  2. a duration versus a point in time
  3. one is defined in terms of the other
  4. time.Now() plus the duration
  5. both hand back a CancelFunc to defer

basics

~10 s

context.WithTimeout takes a duration and expires that far from now; context.WithDeadline takes an absolute time.Time and expires at that instant. WithTimeout is defined as WithDeadline with time.Now() plus the duration.

solid answer

~40 s

They build the same kind of context and differ only in how you express the moment it dies. `context.WithTimeout(parent, d)` is relative and the clock starts when you call it; the standard library documents it as `WithDeadline(parent, time.Now().Add(d))`. `context.WithDeadline(parent, t)` is absolute, which is what you want when the instant came from somewhere else - a cutoff carried in from an upstream caller, or a fixed end time for a batch. Both return a derived context plus a `CancelFunc` you should `defer`, both close `Done()` when the instant passes, and both then report `context.DeadlineExceeded` from `Err()`. `ctx.Deadline()` hands the instant back, with an `ok` flag that is false when no deadline was ever set. Use `WithTimeout` for a local budget on one call, `WithDeadline` when you are re-expressing a budget that already exists.

code

go · 12 lines
go
// Relative: the clock starts here.
ctx1, cancel1 := context.WithTimeout(parent, 2*time.Second)
defer cancel1()

// Absolute: the same instant, spelled out.
ctx2, cancel2 := context.WithDeadline(parent, time.Now().Add(2*time.Second))
defer cancel2()

d, ok := ctx1.Deadline() // ok is true; d is about 2s from now
remaining := time.Until(d)
_, _ = ok, remaining
_ = ctx2

go deeper

for a junior

Be ready to state the difference in one line - duration versus absolute instant - and to write both calls from memory, including the two returned values and the deferred cancel.

for a middle

Explain that WithTimeout is implemented as WithDeadline with time.Now() added, and describe what Done, Err and Deadline report before and after the instant passes.

for a senior

Show judgment about which form to use where: absolute when a budget is being carried in or re-expressed, relative when you are granting a fresh one. Be able to explain what a dropped cancel func actually costs a long-running process.

for a principal

Own the convention across services: whether budgets travel as remaining duration or as instants, how clock skew between machines affects that choice, and what your codebase's default is when a caller supplies no deadline at all.

## The two constructors `context.Context` is Go's standard way to carry a cancellation signal down a call chain. Two of its constructors attach a *time* to that signal: ```go func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) ``` `WithTimeout` takes a **duration** - a length of time, such as `2*time.Second`. `WithDeadline` takes a **`time.Time`** - a specific instant, such as `time.Now().Add(2*time.Second)` or a timestamp parsed out of an incoming request. That is the entire difference. The standard library documents `WithTimeout(parent, d)` as being equivalent to `WithDeadline(parent, time.Now().Add(d))`, and that is how it is implemented. ## What both of them give you Both return two values: the derived context and a `context.CancelFunc`. - **The derived context** has a `Done()` channel that closes when the instant arrives (or when the parent is cancelled, or when you call the cancel function - whichever happens first). - After `Done()` closes because the time ran out, `ctx.Err()` returns `context.DeadlineExceeded`. If it closed because someone called the cancel function instead, `Err()` returns `context.Canceled`. Before it closes, `Err()` is nil. - `ctx.Deadline()` returns `(deadline time.Time, ok bool)`. On a context built by either constructor `ok` is true and `deadline` is the instant. On a context with no deadline anywhere in its chain - `context.Background()`, for example - `ok` is false and the `time.Time` is the zero value. - The `CancelFunc` should be called, normally with `defer cancel()`, even though the deadline will fire on its own. Calling it releases the timer and detaches the child from its parent immediately; dropping it keeps both alive until the deadline passes, which on a long-lived parent is a slow leak. `go vet`'s `lostcancel` check exists precisely to catch a cancel function that is never called. ## Which one to reach for **Use `WithTimeout` when you are granting a fresh budget.** "This call gets 2 seconds" is a duration, and writing it as a duration is the honest expression. The clock starts at the moment of the call, not when the surrounding request arrived, which is exactly what you want for a locally scoped operation. **Use `WithDeadline` when the instant already exists somewhere else.** Typical cases: an RPC framing that carries an absolute expiry, a batch that must be finished before a downstream job starts at 04:00, or code that reads `parent.Deadline()` and wants to derive a slightly earlier instant so it can reserve a few hundred milliseconds to write an error response. Recomputing that as a duration means measuring `time.Until(instant)` and then having `WithTimeout` add it back to `time.Now()` - two conversions where zero were needed, and two chances to lose milliseconds. The distinction matters most when a value is *transported*. A duration is only meaningful relative to when it was created; ship "2 seconds" across a network hop and the receiver has no idea how much of it the wire already ate. An absolute instant survives the trip (modulo clock skew between the two machines, which is why real systems usually send the remaining duration on the wire and convert back to a deadline on arrival). ## The relationship between the two Because `WithTimeout` is literally `WithDeadline` with the arithmetic done for you, everything true of one is true of the other: the same derivation tree, the same `Err()` values, the same requirement to call cancel, and the same fact that a derived context can only ever make the effective deadline **earlier** than the parent's, never later. ## Common confusions - *"The timeout starts when the request arrives."* No - it starts when you call `WithTimeout`. Any work done before that call is outside the budget you just granted. - *"If a timeout is set, I do not need the cancel function."* You still do; see above. - *"The deadline kills the goroutine doing the work."* It does not. Nothing in Go stops a goroutine from the outside. A context deadline closes a channel and sets an error; work stops only because some code checks `ctx.Done()` or `ctx.Err()`, or because the ctx was handed to a library that does. - *"WithDeadline takes a duration."* It takes a `time.Time`. Passing `2*time.Second` there does not compile - a small mercy, since the two argument types make the mistake impossible to ship.

  • An upstream caller hands you the instant by which it needs an answer. Which constructor do you use?
    `context.WithDeadline`, with that instant directly. Converting it to a duration and using `WithTimeout` means measuring `time.Until(instant)` and letting `WithTimeout` add it back to `time.Now()` - the same result with two extra conversions and a little lost time. Keep an absolute value absolute for as long as you can.
  • The context will expire on its own, so why must you still call the cancel function?
    Because expiry is not the only exit. If the work finishes in 5 ms, the derived context stays attached to its parent and its timer stays armed until the full duration elapses. On a long-lived parent that is an accumulating leak of contexts and timers. `defer cancel()` releases both the moment you are done, and `go vet`'s `lostcancel` check flags the missing call.
  • What does ctx.Deadline() return for context.Background()?
    The zero `time.Time` and `ok == false`, meaning no deadline exists anywhere in the chain. That boolean is the right way to ask whether your caller gave you a budget at all - do not test the `time.Time` against the zero value yourself, and do not assume a missing deadline means infinite patience.

saying these in an interview costs you the question

  • Says WithDeadline takes a duration argument
  • Thinks the WithTimeout clock starts when the request arrived
  • Believes the returned cancel func is optional once a timeout is set
  • Claims the deadline kills the goroutine doing the work
  • Cannot say what Deadline() reports when no deadline exists