package store
import (
"context"
"database/sql"
)
type Order struct {
ID string
Status string
Total int64
}
type OrderStore struct {
db *sql.DB
}
func NewOrderStore(db *sql.DB) *OrderStore {
return &OrderStore{db: db}
}
func (s *OrderStore) GetByID(ctx context.Context, id string) (*Order, error) {
const q = `SELECT id, status, total_cents FROM orders WHERE id = $1`
var o Order
err := s.db.QueryRowContext(ctx, q, id).Scan(&o.ID, &o.Status, &o.Total)
if err != nil {
return nil, err
}
return &o, nil
}
package server
import (
"context"
"database/sql"
"encoding/json"
"errors"
"net/http"
"example.com/app/store"
)
type OrdersHandler struct {
Orders *store.OrderStore
}
func (h *OrdersHandler) GetOrder(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
order, err := h.Orders.GetByID(r.Context(), id)
switch {
case errors.Is(err, context.DeadlineExceeded):
http.Error(w, "request timed out", http.StatusGatewayTimeout)
return
case errors.Is(err, sql.ErrNoRows):
http.Error(w, "order not found", http.StatusNotFound)
return
case err != nil:
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(order)
}
package server
import (
"context"
"net/http"
"time"
)
func TimeoutMiddleware(d time.Duration) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), d)
defer cancel()
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
This snippet shows how a per-request timeout is enforced end to end in a Go service by attaching a deadline to the request's context.Context and letting every downstream call observe it. The key idea is that Go does not interrupt running goroutines; instead, cooperating APIs check the context and abort when it is cancelled. By deriving a child context with context.WithTimeout at the edge and threading it through the store and the database driver, a slow query no longer holds a connection open indefinitely — it is cancelled precisely when the request budget expires.
In timeout middleware, TimeoutMiddleware wraps a handler and replaces the incoming request context with one produced by context.WithTimeout. The defer cancel() is essential: it releases the timer and any resources associated with the context once the handler returns, even on the fast path, avoiding a goroutine and timer leak. The middleware is deliberately thin — it does not itself write a timeout response, because the deadline may fire deep inside a handler where the writer is better positioned to react.
OrderStore demonstrates propagation into the data layer. Every method takes ctx as its first argument and passes it to QueryRowContext, so the database/sql driver can send a cancellation to Postgres when the deadline passes. This is the payoff of the pattern: the timeout is not advisory, it actually tears down in-flight work rather than letting it complete unnoticed.
orders handler ties it together. GetOrder calls the store with r.Context(), which already carries the deadline installed by the middleware. When a lookup exceeds the budget, the error is inspected with errors.Is(err, context.DeadlineExceeded) and translated into a 504 Gateway Timeout; a genuine missing row maps to 404. Distinguishing these cases matters, because a deadline error is a server-side capacity signal, not a client error.
The trade-off is discipline: the pattern only works if every blocking call accepts and honors a context, so a single ctx-less call becomes a silent hole where the timeout is ignored. It also means the deadline is shared across all downstream operations in a request, so a slow first query can starve later ones. Reaching for this approach makes sense whenever a service must bound tail latency and protect scarce resources like connection pool slots.
Related snips
package api
import (
"net/http"
"runtime/debug"
)
Expose build metadata for debugging deploys
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
package dbutil
import (
"context"
"github.com/jackc/pgconn"
Retry Postgres serialization failures with bounded attempts
use crossbeam::channel::unbounded;
use std::thread;
fn main() {
let (tx, rx) = unbounded();
Crossbeam for advanced concurrent data structures
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
Promises and async/await patterns for asynchronous JavaScript
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
Channels (mpsc) for message passing between threads
Share this code
Here's the card — post it anywhere.