Compare
Symptom
Human

One function handles multiple orthogonal concerns sequentially. Adding a new concern (CORS, body parsing, request tracing) means editing the same function; the function changes for entirely unrelated reasons; reviewing it requires holding the full concern set in working memory.

Agent

A single-function dispatcher with multiple concerns the agent must verify against on every concern-related edit. Edits to one concern's logic touch the same source span as every other concern; merge conflicts and unintended side effects are routine.

Goal
Human

Each concern is a small handler class that decides whether to handle the request or forward it down the chain. The chain composition is explicit at the construction site; adding a new concern is a new handler class + an additional link in the chain.

Agent

N small handler classes the agent reads one at a time. Edits to a concern scope to one file; chain ordering is one expression at the construction site; the agent's verification budget on a concern-specific change drops from full-function-scan to one-file-scan.

Before the pattern

function handleRequest(req, res) {
if (!req.headers.authorization) {
res.status = 401;
res.body = 'unauthorized';
return;
}
if (rateLimiter.exceeded(req.ip)) {
res.status = 429;
res.body = 'too many requests';
return;
}
logger.info(`${req.method} ${req.path}`);
if (req.path === '/api/users') {
res.status = 200;
res.body = listUsers();
return;
}
res.status = 404;
}
// One function changes for auth reasons, rate-limit reasons, logging reasons,
// routing reasons — every concern is a Divergent Change pressure on the same body.

After the pattern

class Handler {
constructor(next) {
this.next = next;
}
handle(req, res) {
if (this.next) this.next.handle(req, res);
}
}
class AuthHandler extends Handler {
handle(req, res) {
if (!req.headers.authorization) {
res.status = 401;
res.body = 'unauthorized';
return;
}
super.handle(req, res);
}
}
class RateLimitHandler extends Handler {
handle(req, res) {
if (rateLimiter.exceeded(req.ip)) {
res.status = 429;
res.body = 'too many requests';
return;
}
super.handle(req, res);
}
}
class LoggingHandler extends Handler {
handle(req, res) {
logger.info(`${req.method} ${req.path}`);
super.handle(req, res);
}
}
class RoutingHandler extends Handler {
handle(req, res) {
if (req.path === '/api/users') {
res.status = 200;
res.body = listUsers();
return;
}
res.status = 404;
}
}
const chain = new AuthHandler(new RateLimitHandler(new LoggingHandler(new RoutingHandler(null))));
chain.handle(req, res);
Example source: Illustrative example written for this site in the spirit of Design Patterns (Gamma, Helm, Johnson, Vlissides, Addison-Wesley, 1994), chapter 5. The book's running example is a context-sensitive help system where help requests bubble up a widget tree; this JavaScript adaptation uses HTTP middleware because the chain-of-handlers shape is recognizable to most modern readers.
Pressure
Human

A single handleRequest function changing for auth reasons, rate-limit reasons, and logging reasons in the same week is Divergent Change at its worst — the function's git history is incoherent, code review becomes change-by-change instead of body-by-body, and conflicts during merges multiply.

Agent

A long sequential handler function is O(concerns) read cost per edit and O(concerns²) verification cost for concern interactions. The agent's working memory is consumed by the full concern set rather than the change's actual scope.

Tradeoff
Human

Chain composition is dynamic — the order matters (logging before or after auth changes what gets logged for unauthorized requests), and the ordering lives at the construction site, not in the handler classes themselves. Stack traces span every handler in the chain; debugging requires understanding the wiring.

Agent

Chain composition is implicit in the construction expression's nesting order. The agent investigating a runtime issue must trace through N handlers; stack traces span N frames; concern interactions (handler-A short-circuits before handler-B logs the failure) require explicit chain-aware reasoning the type system cannot enforce.

Relief
Human

Each handler is small, single-responsibility, unit-testable; the chain is composable; new concerns ship as new handlers without touching existing ones. Tests for each handler cover its own behaviour; chain-level tests cover ordering interactions explicitly.

Agent

Adding a new concern is one new handler class plus one wiring entry in the chain; per-handler tests load one class instead of the full chain, and the chain's composition lives at one construction site the agent reads to predict ordering.

Trap
Human

Handlers that look at chain state (which handler ran before me, will the next handler bail out, did the previous handler short-circuit) reintroduce coupling the pattern was supposed to eliminate. Chain ordering becomes structural; replacing one handler requires understanding the full chain's behaviour. The chain becomes a thinly-disguised long function spread across files.

Agent

Handlers that peek at chain neighbors or skip ahead by mutating the request reintroduce cross-handler coupling. The agent reading one handler can no longer reason about its behaviour in isolation; chain-aware verification becomes mandatory on every handler edit, defeating the per-handler isolation the pattern promised.