Pipes & Filters
Pipes & Filters decomposes a complex processing task into a sequence of small, single-purpose stages (filters) connected by channels (pipes). Each filter reads input, transforms it, and writes output — without knowing what's upstream or downstream. Unix pipes (`cat file | grep | sort | uniq`) are the canonical example; modern equivalents include ETL pipelines, stream processing (Kafka Streams, Flink), and CI/CD workflows. The pattern trades monolithic complexity for composability: filters can be reused, reordered, parallelized, and tested independently.
How it works
Pipes & Filters is one of the oldest architectural patterns — described in the 1996 Pattern-Oriented Software Architecture book and prefigured by Unix shells in the 1970s. The idea is to split a complex transformation into a chain of simple filters connected by pipes.
- Filter: a component that consumes input, performs one transformation, and produces output. Crucially, a filter is self-contained — it doesn't know what produced its input or what consumes its output. Filters should be reusable and composable.
- Pipe: a connector that moves data between filters. Pipes can be in-process queues, OS pipes, message brokers (Kafka, RabbitMQ), or files. The pipe provides buffering, decoupling producers and consumers.
Canonical example (Unix):
cat access.log | grep '500' | awk '{print $1}' | sort | uniq -c | sort -rn | head -10Each command is a filter; the | is the pipe. Each filter does one thing well; combined, they form a complex transformation.
Modern equivalents:
- ETL: extract from source → transform (clean, enrich, validate) → load into warehouse. Each stage is a filter; queues (Kafka, S3) are pipes.
- Stream processing: Kafka topic → parse → filter → enrich → aggregate → sink. Flink/Spark topologies are pipes-and-filters graphs.
- CI/CD: checkout → build → test → package → deploy. Each job is a filter; artifact storage is the pipe.
- Media processing: decode → resize → watermark → encode. Each filter is a separate process/container.
The pattern's power comes from composability: a filter written for one pipeline can be reused in another, because it has a clean input/output contract and no knowledge of context.
Variants of the pattern:
- Linear pipeline — filters in a straight line, like the Unix example. Simplest form.
- Branching pipeline — one filter's output goes to multiple downstream filters (fan-out). Used for parallel processing or for routing to different sinks.
- Merging pipeline — multiple filters' outputs combine into one downstream filter (fan-in). Used for joining data from multiple sources.
- Cyclic pipeline — output of one filter feeds back to an earlier filter. Used for iterative algorithms (training loops, fixpoint computations).
- Batch vs streaming — pipes can carry batches (one file per stage) or streams (one event at a time). Streaming pipelines (Kafka Streams, Flink) are pipes-and-filters applied to infinite streams.
Key design questions when building a pipeline:
- Where does state live? Stateless filters are easy to parallelize and recover. Stateful filters (aggregations, joins) need checkpointing.
- What's the failure model? Should a failed filter retry, dead-letter, or abort the pipeline? Idempotency matters — reprocessing must be safe.
- What's the back-pressure strategy? If a downstream filter is slow, the upstream filter must slow down (blocking) or buffer (memory/disk risk).
- How is the pipeline observed? Per-stage throughput, error rate, queue depth — without these, you can't find the bottleneck.
- What's the granularity of the filter? Too coarse → monolith. Too fine → overhead per stage exceeds the work.
Doug McIlroy's 1978 Unix philosophy — 'write programs that do one thing and do it well; write programs to work together; write programs to handle text streams, because that is a universal interface' — is Pipes & Filters elevated to an operating system design principle. The success of Unix shells is the success of the pattern: a small set of composable filters (grep, sort, awk, sed, cut) generates combinatorial power. Every modern stream-processing framework is, in effect, a reimplementation of Unix pipes for distributed, fault-tolerant, infinite streams.
Strengths:
- Composability — filters are reusable across pipelines. A
parse-jsonfilter is useful in dozens of pipelines. - Testability — each filter has a clear input/output contract; unit test it in isolation.
- Parallelism — independent filters can run in parallel; multiple consumers of a pipe can scale a slow stage.
- Decoupling — filters don't know each other; replacing one doesn't affect others (as long as the contract holds).
- Observability — per-stage metrics reveal the bottleneck.
- Heterogeneous tech — filters can be written in different languages; pipes are language-agnostic (JSON, Protobuf, Avro).
Weaknesses:
- Throughput cap = slowest stage — like any pipeline, the slowest filter sets the rate. Speeding up other stages does nothing.
- Latency overhead — each stage adds serialization, queueing, and processing latency. A 10-stage pipeline has 10× the per-event latency of a monolithic function.
- Error handling complexity — what happens when filter #4 fails? Retry just that stage? Replay from filter #1? Dead-letter? Each model has trade-offs.
- Operational complexity — many moving parts to deploy, monitor, and version.
- Schema evolution — a change to a filter's output schema affects every downstream filter.
- Over-decomposition — pipelines with 50 stages where 5 would do; per-stage overhead exceeds the work.
In a pipes-and-filters pipeline, one filter processes 10 events/sec while all others process 100 events/sec. What is the pipeline's maximum throughput, and what are the options to fix it?
Pick one answer.
Why is the Unix shell considered the canonical realization of Pipes & Filters?
Pick one answer.
Engineering mental model
Mental model. Think of Pipes & Filters as a deliberate boundary in a system. The boundary exists because something becomes harder to manage when everything is done in one place: latency, scale, failure isolation, consistency, cost, or team ownership. The useful question is not “what does Pipes & Filters mean?” but “what pressure makes this boundary worth introducing, and what new failure mode does it create?”
Before choosing Pipes & Filters, name the workload, the critical user path, the dominant bottleneck, the failure you are trying to absorb, and the trade-off you are willing to accept. If you cannot name those five things, the design is probably premature.
// Pseudocode
request = receive()
result = pipes_and_filters(request)
return result
// Production questions:
// 1. What happens on timeout?
// 2. Can this operation be retried safely?
// 3. What is the bottleneck?Back-of-the-envelope reasoning
Numerical lens: write down traffic, payload size, read/write ratio, peak multiplier and durability target before choosing a component. The numbers should justify the architecture.
Interactive thought experiment: Pipes & Filters
Change the variables below and predict what breaks first in Pipes & Filters. The production lab can later reuse these same inputs.
Change one variable at a time. Predict the failure mode first, then move the slider and see whether your mental model matches the simplified system response.
If you are stuck on Pipes & Filters, start by drawing the request path and marking every network hop, stateful component, queue, cache and failure boundary. Then estimate where the system will saturate.
You increase traffic by 10× in a system using Pipes & Filters. What should you inspect first?
Pick one answer.
Which statement is the safest engineering habit when using Pipes & Filters?
Pick one answer.
You have dashboards for traffic, latency, errors and saturation. You can change the architecture, but every change has operational cost.
Production scenario: your system uses Pipes & Filters, traffic suddenly spikes, and p99 latency doubles. What is your first move?
Interview drill
Answer this without notes: When would you choose Pipes & Filters, and when would you intentionally avoid it? Mention at least one bottleneck it addresses, one failure mode it introduces, and one alternative. Then quantify the workload you are designing for.
A useful engineering lens for Pipes & Filters: define the problem it solves, the simpler design that fails first, the constraint that forces you to introduce this concept, and the new failure modes the concept creates.
Numerical sanity check
Back-of-the-envelope reasoning beats fake precision. State your traffic, payload, concurrency and growth assumptions explicitly, then calculate enough to know whether the current architecture is orders of magnitude away from the target.
Do not optimize for a memorized definition. Reason from the workload and failure mode.
Imagine the simplest version of a system using Pipes & Filters. What breaks first as traffic grows by 10×, and what would you change before reaching 100×?
Pick one answer.
What you gain, what you pay
- +Composability — filters are reusable across pipelines.
- +Testability — each filter has a clear input/output contract.
- +Parallelism — independent filters and multiple-consumer pipes scale horizontally.
- +Decoupling — filters don't know each other; replacing one doesn't break others.
- +Heterogeneous tech — filters can be different languages, pipes are language-agnostic.
- −Throughput capped by the slowest filter; speeding up others is wasted effort.
- −Per-stage latency overhead — many small filters add up to high end-to-end latency.
- −Error handling is hard — per-stage retry, replay, or dead-letter, each with trade-offs.
- −Operational complexity — many moving parts to deploy, monitor, and version.
- −Schema evolution affects every downstream filter.
How this breaks in production
- Slow filter caps throughput while fast filters idle — the pipeline looks busy but produces little.
- Filter crash loses in-flight events — needs idempotent replay and durable pipes.
- Out-of-order delivery — downstream filter sees events in wrong order, breaks aggregations.
- Back-pressure not propagated — upstream keeps producing while downstream drowns; queue grows unbounded.
- Schema drift — upstream filter changes output format, downstream filter silently misbehaves.
- Over-decomposition — 50 filters where 5 would do, per-stage overhead exceeds work.
Don't fall into these traps
- •Optimizing non-bottleneck filters — measure first, optimize the slowest stage.
- •Not making filters idempotent — reprocessing must be safe, or replay causes duplicates.
- •Using in-memory pipes for cross-process pipelines — crashes lose data; use durable queues.
- •Forgetting back-pressure — without it, queues grow until OOM.
- •Tight coupling between filters — sharing types, schema, or context defeats the pattern's composability.
- •Designing pipelines without per-stage metrics — you can't find the bottleneck without throughput and queue-depth per stage.
Real systems using this
How real systems implement this
- Unix shell pipelines — The original and still canonical example: each command is a filter, `|` is the pipe, and the text-stream interface makes filters composable. McIlroy's 1978 design is the template every modern framework imitates.
- Apache Kafka + Kafka Streams / Flink — Kafka topics are pipes (durable, partitioned, replayable); Kafka Streams and Flink topologies are filters connected by topic. The model scales to trillions of events/day and supports exactly-once semantics, stateful processing, and rebalancing.
- Airflow / dbt pipelines — ETL pipelines in Airflow are pipes-and-filters applied to batch data: extract from source (filter), transform (filter), load (filter). Pipes are typically S3/GCS object storage or warehouse tables. dbt applies the same model inside the warehouse.
Practice saying it out loud
- Q1What is the Pipes & Filters pattern? Give an example from Unix and one from a modern stream-processing framework.
- Q2In a 5-stage pipeline, stage 3 is the slowest. What is the pipeline's throughput, and how do you improve it?
- Q3How do you handle failures in a pipes-and-filters pipeline? Compare retry, replay, and dead-letter strategies.
- Q4When does Pipes & Filters add more overhead than value? When is a monolithic function preferable?
- Q5How does back-pressure work in a pipes-and-filters pipeline, and why does it matter?
Further reading & references
Core explanations are original NO CAP material. External references are provided for deeper study and standards.
What next?
Mark as understood once the mental model clicks.
Next recommended
Queue-Based Load Leveling