AfterFunc

April 25, 2025

One of the drawbacks of this format I’ve chosen—reading through a document, and providing commentaty, in order—is that sometimes advanced topics come up before the foundational ones they rely on.

Today is one such case.

Typically, context cancelation is used to terminate an action in progress. But it can also be used to begin an action. One way to accomplish this is to wait for a cancelation signal in a goroutine:

go func() {
	<- ctx.Done()
	doSomething()
}()

But since Go 1.21, there’s a more flexible option:

Functions

func AfterFunc

func AfterFunc(ctx Context, f func()) (stop func() bool)

AfterFunc arranges to call f in its own goroutine after ctx is canceled. If ctx is already canceled, AfterFunc calls f immediately in its own goroutine.

Multiple calls to AfterFunc on a context operate independently; one does not replace another.

This means we could rewrite our earlier code as:

context.AfterFunc(ctx, func() {
	doSomething()
})

But with additional capabilities…

Calling the returned stop function stops the association of ctx with f. It returns true if the call stopped f from being run. If stop returns false, either the context is canceled and f has been started in its own goroutine; or f was already stopped. The stop function does not wait for f to complete before returning. If the caller needs to know whether f is completed, it must coordinate with f explicitly.

stop := context.AfterFunc(ctx func() {
	doSomething()
})

/* ... */

stop() // Actually, I decided not to doSomething() when ctx cancels

If ctx has a “AfterFunc(func()) func() bool” method, AfterFunc will use it to schedule the call.

And finally, if you have a custom context.Context implementation (since it’s an interface, this is possible) that implements a method AfterFunc(func()) func() bool, then that method is used to schedule the function passed to AfterFunc, rather than the default behavior.

The GoDoc provides a few examples, which I’m going to skip over, since they’re rather lengthy.


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


The Context API contract

Today we come to the core of the context package: The Context interface itself. type Context type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key any) any } A Context carries a deadline, a cancellation signal, and other values across API boundaries. Context’s methods may be called by multiple goroutines simultaneously. For clarity, I’ve removed all of the documentation for each of the interface methods in the above quote—we’ll get to those in following emails, and include those there.


Contexts with timeouts

func WithTimeout func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete: func slowOperationWithTimeout(ctx context.Context) (Result, error) { ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) defer cancel() // releases resources if slowOperation completes before timeout elapses return slowOperation(ctx) } Not only is WithTimeout conceptually similar to WithDeadline, it literally is WithDeadline, as we can see form the source code:


Contexts with deadlines

Today we’re talking about canceling contexts based on time. Arguably, this should have come first, as it’s a little simpler to work with than the explicit cancelation we talked about a week ago. But once again, I’m going in essentially alphabetical order, so it’s what we have… func WithDeadline func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) WithDeadline returns a derived context that points to the parent context but has the deadline adjusted to be no later than d.

Get daily content like this in your inbox!

Subscribe