skip to content

questions

21

Run-length encoding replaces repeats with count-symbol pairs, so on what input does it make the output larger?

level: juniorimportance: must knowfreq 66%

answer

  1. depends on how often symbols repeat
  2. count plus symbol costs two bytes
  3. a run of one costs double
  4. break-even at average run length two
  5. alternating input doubles the output

basics

~20 s

Run-length encoding expands any input whose symbols rarely repeat: a single symbol still costs a count plus the symbol, so alternating bytes double in size. It pays only when the average run is longer than one pair.

solid answer

~40 s

Run-length encoding rewrites a stream as `(count, symbol)` pairs. With a one-byte count and a one-byte symbol a pair costs 2 bytes, so a run of length one costs twice what the raw symbol cost. A monochrome scan line of 640 identical pixels collapses beautifully: with the count capped at 255 you emit three pairs, `(255,w)(255,w)(130,w)`, six bytes instead of 640. Feed the same encoder `ABABABAB` and every run has length one, so you get eight pairs — 16 bytes for 8. The break-even is an average run length equal to the pair width in symbols, which is two here. That is why real formats guard the encoder: a literal-span marker, or a per-block flag that stores the block raw when encoding grew it.

code

pseudocode · 10 lines
pseudocode
i = 0
while i < length(input):
    run = 1
    while i + run < length(input)
          and input[i + run] == input[i]
          and run < 255:
        run = run + 1
    emit(run)
    emit(input[i])          # two bytes, even when run == 1
    i = i + run

go deeper

for a junior

Recall the shape of the output — a count and a symbol per run — and the one-line consequence: data that does not repeat gets bigger, roughly doubling when every run has length one.

for a middle

Explain the break-even. A pair costs two bytes and covers one run, so the encoder wins only above an average run length of two, and the count's fixed width forces long runs to split.

for a senior

Show the operational guard: measure the encoded block against the raw block, keep the smaller, and record the choice. Know which payloads genuinely carry runs and which, such as already-compressed bytes, never will.

for a principal

Frame it as a stage, not a compressor. The judgment is whether a cheap repetition-only pass earns its place ahead of a general coder for a given payload class, given the expansion risk on everything else.

## What the encoder actually emits Run-length encoding is the simplest lossless scheme there is. Walk the input, count how many times the current symbol repeats **consecutively**, emit that count followed by the symbol, and continue from the first symbol that differs. Decoding is the mirror image: read a count, read a symbol, write the symbol that many times. It carries **no model** of which symbols are likely. It exploits exactly one kind of redundancy — adjacent repetition — and is blind to every other kind. Two consequences follow immediately: - A **run of length one still costs a whole pair**. With a one-byte count and a one-byte symbol that is 2 bytes spent to represent 1 byte of input. - The **count field has a fixed width**, so any run longer than that field's maximum must be split across several pairs. ## The break-even arithmetic Let `p` be the pair width in bytes (count plus symbol) and `r` the average run length in symbols. The encoder emits roughly `n/r` pairs for `n` input symbols, so the output is about `(n/r) * p` bytes against `n` raw. The encoder wins when `r > p`, breaks even at `r = p`, and loses below it. With a one-byte count and a one-byte symbol, `p = 2`: an average run of two is the knife edge. | Input | Raw bytes | Run-length output | Outcome | |---|---|---|---| | 640 identical pixels | 640 | 6 — `(255,w)(255,w)(130,w)` | about 1% of raw | | `AAAB` | 4 | 4 — `(3,A)(1,B)` | no change | | `ABABABAB` | 8 | 16 — eight pairs | doubled | | uniformly random bytes | n | about 2n | doubled | The random-byte row is the honest worst case and it is not a corner case: with 256 equally likely symbols the chance that the next byte repeats the current one is 1/256, so the expected run length is barely above one and essentially every pair covers a single symbol. ## Where the worst case actually bites The inputs an engineer meets most often sit near that worst case. Ordinary prose, compiled instruction streams and structured text all have average run lengths close to one. **Already-compressed bytes are the sharpest trap**: a good coder has removed exactly the long runs the encoder is hunting, so re-encoding it is pure expansion. Run-length encoding is therefore applied where runs are known in advance to exist: - bilevel raster scan buffers, where a scan line crosses long stretches of one colour; - sparse bitmaps and occupancy maps dominated by one value; - padding regions and long spans of zero bytes inside a fixed-layout record; - the output of an earlier stage that has been arranged to produce runs. ## The guards real formats carry 1. **A store-raw escape per block.** The encoder compresses a block, compares the result against the input, and if the result is larger it emits a flag meaning "this block is literal". That caps the damage at the flag itself rather than at 100% growth. 2. **Literal spans.** A marked span saying "the next k symbols are not encoded" lets isolated symbols cost about one byte each plus an amortised marker, instead of two bytes each. 3. **Splitting on the count's width.** A run of 640 with a one-byte count becomes 255, 255 and 130 — the encoder must handle the split, and the decoder must tolerate two adjacent pairs carrying the same symbol. 4. **Implied alternation.** When only two symbols exist and runs alternate by construction, the symbol is not stored at all: you store the first colour once and then nothing but lengths, halving the per-pair cost. ## What it leaves on the table Even where run-length encoding wins outright, it does not touch the distribution of what it produces. Run lengths are themselves far from uniform — short runs are much more common than long ones — and the count bytes it emits are ordinary symbols with an ordinary skew. That residual redundancy belongs to an entropy coder placed behind it, which is why run-length encoding appears in practice as one stage of a pipeline rather than as a whole compressor. Ecosystems differ in where they expose it — some formats offer it as a standalone mode, others only as one token type inside a larger scheme — but the arithmetic above is the same in every one of them.

  • How do real formats stop a run-length stage from inflating a block?
    They compare the encoded block against the raw block and keep whichever is smaller, recording the choice in a per-block flag. Some also carry a literal-span token so isolated symbols cost about one byte each instead of a full pair. Both cap the worst case at a few bits of overhead rather than doubling.
  • Why can a bilevel scan line store lengths only, with no symbols at all?
    With two symbols, adjacent runs must alternate by construction — a run of white can only be followed by a run of black. So the symbol carries no information beyond the first one: you store the starting colour once and then a sequence of lengths, which roughly halves the per-run cost.
  • Why does re-running the encoder over its own output usually make things worse?
    The first pass removed the adjacent repetition, so the second pass sees counts and symbols interleaved with almost no runs. Average run length is back near one, and every symbol costs a fresh pair. Repeated application of a transform is not cumulative gain; it is a reliable way to expand.

saying these in an interview costs you the question

  • Claims run-length encoding always shrinks data, never expands it.
  • Thinks the worst case is merely no gain rather than growth.
  • Applies it to already-compressed bytes and expects a win.
  • Forgets the count field has a maximum, so long runs split.
  • Believes it models symbol frequencies the way an entropy coder does.
open as a page

How does an arithmetic coder turn a whole message into a single subinterval of [0,1)?

level: middleimportance: must knowfreq 50%

basics

~20 s

An arithmetic coder starts with [0,1) and narrows it once per symbol, keeping the slice whose width is that symbol's probability. The final interval's width is the message's probability, and a number inside it identifies the whole message.

open as a page

How does a sliding-window compressor encode a repeated byte sequence as a length-distance back-reference?

level: middleimportance: must knowfreq 65%

basics

~20 s

A sliding-window compressor keeps the most recent N bytes it has already produced as its window. When the next bytes repeat something inside that window, it emits a pair of numbers, a distance back and a match length, instead of the bytes.

open as a page

How does Huffman coding build a code tree from a table of next-hop identifier frequencies?

level: middleimportance: must knowfreq 65%

basics

~20 s

Huffman coding repeatedly removes the two lowest-frequency trees and joins them under a new node weighted by their sum, until one tree remains. Each symbol's code is its root-to-leaf path, so rare symbols sink deepest and frequent ones stay shallow.

open as a page

The Burrows-Wheeler transform and move-to-front emit as many symbols as they consume, so what do they buy a compressor?

level: middleimportance: must knowfreq 55%

basics

~20 s

Nothing by themselves — they remove no bytes at all. They reshape the symbol distribution so that the entropy coder behind them, which is the stage that actually removes bits, meets a far more skewed and predictable stream.

open as a page

Why can an arithmetic coder spend about 0.15 bits on a symbol whose probability is 0.9?

level: seniorimportance: must knowfreq 60%

basics

~20 s

Because a symbol costs a multiplication of the interval, not an appended codeword. Narrowing by 0.9 consumes log2(1/0.9) = 0.152 bits of the final number, and the rounding to whole bits happens once for the message, not per symbol.

open as a page

Why does a sliding-window matcher emit short repeats as literals rather than as back-references?

level: middleimportance: should knowfreq 38%

basics

~20 s

A match token is not free: it carries a distance and a length, roughly 21 bits in a scheme with a 32 KB window. Replacing two bytes with it costs more than the 16 bits those literals need, so schemes set a minimum match length below which literals win.

open as a page

Why does a sliding-window compressor with a 32 KB window miss a repeat that last occurred 100 KB earlier?

level: middleimportance: should knowfreq 48%

basics

~20 s

A sliding window holds only the most recent N bytes — 32768 of them at 32 KB. A repeat 100 KB back has already slid out and no distance can address it, so the compressor re-emits those bytes as literals. The output stays correct, just larger.

open as a page

What does a canonical Huffman code let an encoder ship to the decoder instead of the tree itself?

level: middleimportance: should knowfreq 42%

basics

~20 s

Only one code length per symbol, in an agreed symbol order. A canonical code assigns the actual bit patterns from the lengths alone by a fixed rule, so both sides derive identical code words without any tree structure crossing the wire.

open as a page

A sorted list of rising identifiers is delta coded, so what does a single out-of-order value cost?

level: middleimportance: should knowfreq 42%

basics

~20 s

One out-of-order value costs two wide differences, not one — the drop down and the climb back up — and forces the whole stream to carry signed values, so every small difference then pays for a sign.

open as a page

In an adaptive arithmetic coder, how does the decoder's probability model stay identical to the encoder's?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Both sides code a symbol with the model's current probabilities, then apply the same update, so the model depends only on already-coded history. The decoder rebuilds it step for step, so no table is transmitted, provided the arithmetic is deterministic.

open as a page

With fixed-width registers, how does an arithmetic coder keep coding once its interval straddles the midpoint?

level: seniorimportance: should knowfreq 36%

basics

~20 s

By renormalising. Settled leading bits are emitted and the interval doubled; when it is narrow but astride the midpoint, the coder doubles about the midpoint and counts one deferred bit, flushed later as complements of the next settled bit.

open as a page

Why is the match stage of a sliding-window compressor normally followed by an entropy coder?

level: seniorimportance: should knowfreq 44%

basics

~20 s

The two stages remove different redundancies. Matching removes repeated sequences but still writes each literal, length and distance as a symbol; those symbols are heavily skewed, and coding frequent ones in fewer bits is what the entropy stage does. Neither stage can do the other's job.

open as a page

Why does a Huffman code spend a full bit per symbol when one next hop carries 95% of the traffic?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Because a symbol code emits whole bits, and the shortest possible code word is one bit. A two-symbol alphabet therefore costs exactly 1 bit per symbol while the source's Shannon entropy is about 0.29 bits, wasting roughly 0.71 bits every time.

open as a page

Your archive stores each document as one adaptive arithmetic-coded stream — what durability risks does that create?

level: principalimportance: should knowfreq 30%

basics

~20 s

Three: the exact model update rule becomes part of the format and must be versioned; a corrupted bit desynchronises everything after it; and nothing reads without decoding from the start. Independent blocks and verified writes bound all three.

open as a page

A pre-trained compression dictionary would shrink millions of tiny messages that never fill a sliding window — what does adopting one commit both ends to?

level: principalimportance: should knowfreq 30%

basics

~20 s

A pre-trained dictionary primes the window with shared content, so a tiny message can reference bytes it never sent. It becomes shared state: both ends must hold identical bytes, it needs an identity carried with the data, readers must have it before writers use it, and archived data stays bound to it.

open as a page

When would you bake one shared Huffman table into every encoder and decoder rather than shipping a table with each message batch?

level: principalimportance: should knowfreq 30%

basics

~20 s

When messages are small enough that a per-message table would eat the saving, and the symbol distribution is stable enough to be fixed at build time. The cost is a versioned contract: a table change must reach encoders and decoders together or decodes corrupt silently.

open as a page

Two implementations build Huffman codes from the same next-hop frequencies and get different code lengths; is one wrong?

level: middleimportance: nice to knowfreq 25%

basics

~20 s

No. When weights tie, different merge choices give genuinely different code trees and different code lengths, yet every one of them is optimal and all share the same expected length. Only determinism matters, and only when both sides rebuild the table independently.

open as a page

What does an asymmetric numeral systems coder change compared with an interval-based arithmetic coder?

level: seniorimportance: nice to knowfreq 24%

basics

~20 s

It replaces the interval's two endpoints with one integer state that grows by roughly 1/p per symbol, making the hot path a table lookup rather than a multiply and divide. Decoding then runs in reverse order, so blocks are buffered.

open as a page

What does a lazy matcher do differently from a greedy one in a sliding-window compressor?

level: seniorimportance: nice to knowfreq 24%

basics

~20 s

A greedy matcher takes the longest match at the current position. A lazy matcher also looks one position ahead: if the match starting at the next byte is longer, it emits the current byte as a literal and takes the better match instead.

open as a page

Why does taking the last column of a string's lexicographically sorted rotations cluster repeated characters together?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

Each rotation's last character is the one that cyclically precedes its first, so sorting rotations groups them by the text that follows. Characters sharing a following context therefore land next to each other in that last column.

open as a page