How do delta, frame-of-reference and bit-packing encodings compress an integer column?
answer
- Make the numbers small, then store them narrowly
- One subtracts a block minimum, one subtracts the predecessor
- Width follows the block's widest residual
- Monotonic ids are the best case
basics
~20 sDelta stores each value as the difference from its predecessor, frame-of-reference stores one per-block minimum plus offsets from it, and bit-packing then writes those small numbers in just enough bits. Composed, a block of rising 64-bit ids can drop to a few bits per row.
solid answer
~50 sAll three shrink integers by making the numbers **smaller**, then storing small numbers in **fewer bits**. Frame-of-reference subtracts a single per-block minimum from every value, so a block of timestamps clustered in one hour becomes offsets in a narrow range. Delta subtracts each value's predecessor instead, which is ideal for monotonic sequences: rising ids become 1s and 2s, and regular ticks become a constant that delta-of-delta reduces to zeros. Bit-packing is the final step common to both: if the largest residual in the block is 15, four bits per value suffice instead of 64. Outliers are handled by storing a few exceptions separately rather than widening every value. Two practical notes: the scope is one block, so a single wild value only widens that block; and delta must be decoded sequentially (a running sum), whereas frame-of-reference plus bit-packing supports direct positional access.
code
text · 6 linesvalues : 1000 1004 1007 1009 1015 -- 5 x 64 bits = 320
frame-of-ref: min=1000 ; 0 4 7 9 15
bit-packed : 4 bits each -- 20 bits + header
delta : 1000 +4 +3 +2 +6 -- residuals shrink
delta-of-delta on a 10s metric: 0 0 0 0 0 -- regular spacing vanishesgo deeper
Recall what each name means literally: difference from the previous value, offset from a block minimum, and storing values in only as many bits as they need.
Explain how they compose into one pipeline, why the reference is per block, and which data shape suits delta versus frame-of-reference.
Bring the operational angle: outliers and sentinels widening a block, delta's loss of positional access, and diagnosing an integer column that refuses to compress.
Frame it as data design — ids and timestamps you control can be made monotonic and dense, which turns into a durable storage and scan-cost reduction across every table that carries them.
## The shared idea An integer column declared as 64-bit almost never needs 64 bits per value. These three encodings exist to close that gap, and they compose into one pipeline: **make the numbers small, then store small numbers narrowly.** ## Frame of reference Pick a reference point for the block — normally the minimum — store it once in the block header, and store every value as its offset from that reference. ``` values : 1000 1004 1007 1009 1015 min : 1000 offsets: 0 4 7 9 15 ``` The offsets now need only enough bits to express the block's *range*, not its magnitude. This is why the scope is per block and not per column: a block covering one hour of Unix timestamps has a range of at most 3600, so twelve bits per value suffice even though the absolute values are ten digits long. A column-wide frame would have a range spanning years and save far less. ## Delta Instead of a shared reference, subtract each value's **predecessor**. Monotonic data is the target: auto-increment ids, insert timestamps, sequence numbers. ``` values : 1000 1004 1007 1009 1015 deltas : 1000 +4 +3 +2 +6 ``` When the spacing is itself regular — a metric sampled every ten seconds — a second differencing pass (delta-of-delta) turns the stream into near-zeros, which is why time-series columns compress extraordinarily well. Delta's weakness is that values may decrease, so residuals are signed; encoders zigzag them into unsigned form so that small negatives stay small. Its structural cost is more important: reconstructing value *n* requires summing everything before it, so a delta block is decoded sequentially. Frame-of-reference keeps positional access — value *n* is at a known bit offset — which matters when the engine wants to fetch a few rows rather than the whole block. ## Bit-packing Neither of the above saves anything until the narrow residuals stop being stored in wide machine words. Bit-packing writes each residual using `ceil(log2(max_residual + 1))` bits, tightly, across word boundaries. ``` offsets 0,4,7,9,15 -> max 15 -> 4 bits each 5 values x 4 bits = 20 bits, versus 5 x 64 = 320 bits ``` The width is stored per block. Unpacking is cheap arithmetic — shifts and masks — which is exactly why these encodings are called "lightweight": their decode cost is small enough that the reduced I/O virtually always wins. ## Outliers and exceptions One extreme value in a block would force every value to the outlier's width and wreck the ratio. The standard fix is to bit-pack at a width that covers the bulk of the values and store the handful of exceeding values separately with their positions — the patched frame-of-reference family. The lesson generalises: **a block's cost is driven by its widest residual**, so an unexpected sentinel value like a far-future timestamp or a `-1` placeholder can quietly cost you several bits per row across an entire block. ## Composition and choice A real encoder chains these: choose delta or frame-of-reference (or both — delta first, then a frame over the deltas), then bit-pack, then hand the result to the general byte codec. Which chain wins depends on the data: - **Sorted or near-sorted, monotonic:** delta, then bit-pack. Best case in the whole family. - **Unsorted but clustered in a narrow range:** frame-of-reference, then bit-pack. Delta would produce large signed swings here. - **Low distinct count regardless of range:** a dictionary over the integers beats both, because the code width follows cardinality rather than range. - **Random wide values — hashes, random ids, encrypted keys:** none of them help. There is no monotonicity, no narrow range, no repetition. The column is stored plain and only the general codec contributes, modestly. Recognising this case is worth more in an interview than reciting the encodings. ## Why sortedness keeps reappearing Every member of this family is sensitive to order. Sorting shrinks deltas to their minimum and narrows each block's range, so the same integer column can differ severalfold in size purely by load order. That is the practical hook: if an integer column compresses badly, ask whether it is monotonic, whether the block range is narrow, and whether a different load order would make either true. ## Interview framing Say the pipeline out loud — *reduce magnitude, then reduce width, then patch the outliers* — and name which input shape suits which reducer. Then add the access-path nuance: delta trades random access for ratio, frame-of-reference does not.
- Why is frame-of-reference computed per block rather than once for the whole column?Because the width depends on the range covered. A block spanning one hour of timestamps has a tiny range and packs into a few bits; a column-wide reference spans the table's whole history, so the offsets stay wide. Small scopes also confine damage: one outlier widens only its own block.
- What is the practical downside of delta encoding compared with frame-of-reference?Delta requires sequential reconstruction — value n is the running sum of every residual before it — so you cannot jump straight to one row inside a block. Frame-of-reference keeps every value at a fixed bit offset, so positional access stays direct. That matters when the engine wants a few rows rather than a full block scan.
- A single sentinel value like 9999999999 sits in an otherwise narrow block. What does it cost?Bit-packing width is set by the widest residual, so that one value can push every row in the block from a few bits to thirty-odd. Encoders mitigate it by packing at a narrower width and storing outliers as positioned exceptions, but the general lesson stands: sentinels and placeholder magic numbers are expensive in columnar storage.
saying these in an interview costs you the question
- Thinks delta encoding works well on unsorted data
- Assumes the reference minimum is global to the column
- Believes bit-packing alone shrinks large absolute values
- Expects these encodings to help random hashes or UUIDs
- Ignores that one outlier widens the whole block