Stream-Table Duality
The equivalence between a stream of changes and a table of current state — each can be derived from the other.
A table is the accumulated result of a stream of changes: replay every change from the beginning and you have current state. A stream is the sequence of changes between successive table states: observe a table over time and you have the changelog.
This is not an analogy; it is the operating principle behind Kafka Streams, ksqlDB, Flink and materialised views generally, and it explains several things that otherwise look unrelated. Log compaction turns a stream into a table by retaining the latest value per key. Change data capture turns a table into a stream by reading its log. Event sourcing stores the stream and derives the table. CQRS projections are stream-to-table.
The practical consequences worth holding onto. State is rebuildable — a stateful stream processor that loses its local store recovers by replaying its changelog, which is why such systems can be stateful without being fragile. And joins have two forms: stream-stream joins need a window because both sides are unbounded, while stream-table joins enrich each event against current state and need none.
Once the duality is visible, the question "should this be a stream or a table" usually resolves to "both, and which do I want to materialise?"