Error Detection and Correction
Every bit sent over a wire, a fiber, or a radio channel travels through a noisy physical medium. Thermal noise, electromagnetic interference, and signal attenuation flip bits silently. So a receiver cannot simply trust what arrives. The Link Layer: Role and Services promises framing. On many links, it also promises reliable delivery of bits within a frame. It cannot keep that promise without some way to detect, or even repair, corruption. This article is about that machinery. The big picture is deceptively simple: the sender adds carefully computed redundant bits to the original data. The receiver uses those extra bits to catch — and sometimes fix — errors. The art lies in choosing redundancy that is cheap to compute and cheap to transmit. It must also be mathematically guaranteed to catch the error patterns that actually occur on real links. These include burst noise from a lightning strike or a scratch on a disk. This matters far beyond a single Ethernet cable. Parity bits protect DRAM, and checksums protect IP and TCP segments. Cyclic redundancy checks (CRC) protect Ethernet and Wi-Fi frames and every ZIP file. Forward error correction (FEC) codes protect deep-space probes, satellite links, QR codes, and modern storage systems like SSDs. The same handful of ideas — parity, checksums, polynomial codes, and Hamming-style correcting codes — recur at every layer of the stack. This article builds the theory from first principles. It covers what a code's minimum Hamming distance guarantees, and how CRC arithmetic actually works bit by bit. It also explains why the Internet checksum is a cheap ones'-complement sum. Finally, it shows how Hamming codes correct single-bit errors outright. Pseudocode, a worked numeric trace, and a circuit-style diagram accompany each idea. These let you compute a CRC or decode a Hamming word by hand.
- Error detection adds redundant bits so a receiver can tell corrupted data from clean data; error correction adds enough redundancy to fix it without retransmission.
- Parity catches all odd numbers of bit flips but misses any even number — cheap but weak.
- The Internet checksum (used in IP, UDP, TCP) is a 16-bit ones'-complement sum, fast in software but blind to some bit-swap patterns.
- CRC (cyclic redundancy check) treats bit strings as polynomials over GF(2) and catches all burst errors up to the length of the generator, making it the workhorse of Ethernet, Wi-Fi, and USB.
- Hamming distance is the mathematical currency of coding theory: a code with minimum distance detects errors and corrects errors.
- Forward error correction (Hamming codes, Reed–Solomon, LDPC) repairs errors on the spot, trading bandwidth for the elimination of retransmission delay — essential when round trips are expensive, as in deep space or storage.
Redundant bits, chosen so that valid codewords are far apart in Hamming distance, let a receiver detect or even repair bit errors without asking the sender to resend anything.
The idea
An error-detecting code appends redundant bits, computed from the data by a deterministic function agreed upon by sender and receiver, so that the receiver can recompute the function and compare. If corruption changed the data but the redundant bits still match by the function's rule, the error is invisible — a miss. The central design goal is to choose a redundancy scheme whose blind spots do not match the error patterns real channels actually produce. The unifying concept is Hamming distance: the number of bit positions in which two equal-length bit strings differ. A code is a set of allowed bit strings (codewords); its minimum distance is the smallest Hamming distance between any two distinct codewords. This single number tells you everything about the code's power. If , the code can detect up to bit errors (any smaller number of flips cannot turn one codeword into another), and it can correct up to errors (each codeword owns an exclusive "ball" of that radius, so a corrupted word still lands closest to its true origin). Three broad families sit on this foundation. Parity and checksums are lightweight, additive schemes — a single parity bit, or a running sum like the Internet checksum — good for catching common, low-multiplicity errors cheaply, but with well-known blind spots. CRC (cyclic redundancy check) treats the frame as a polynomial over the two-element field GF(2) and divides it by a fixed generator polynomial; the remainder is the redundancy. CRCs are chosen so that is large enough to catch all burst errors up to the generator's degree, and they dominate link-layer framing. Forward error correction (FEC) goes further: codes such as Hamming, Reed–Solomon, and low-density parity-check (LDPC) codes are constructed so the receiver can not only detect but reconstruct the original data, at the cost of sending more redundant bits per message.
How it works
Parity — the atomic building block
A single parity bit appended to data bits makes the total number of 1-bits even (even parity) or odd (odd parity). The receiver recomputes the parity of the received bits; a mismatch means at least one bit flipped. ``` function COMPUTE_PARITY(data_bits[1..k]): p = 0 for i in 1..k: p = p XOR data_bits[i] return p // append p as bit k+1 function CHECK_PARITY(received_bits[1..k+1]): x = 0 for i in 1..k+1: x = x XOR received_bits[i] return (x == 0) // true means "no error detected" ``` Parity has : it detects exactly one flip (distance 1 apart from any other codeword) but a second flip restores overall parity, becoming invisible. Two-dimensional parity (a parity bit per row and per column of a data matrix) raises to 4 and can even locate and correct a single-bit error, because the failing row and failing column pinpoint the exact bit.
The Internet checksum
Used by IP, UDP, and TCP, the Internet checksum treats the packet as a sequence of 16-bit words, sums them with ones'-complement addition (end-around carry), and sends the ones'-complement of that sum. The receiver sums all words including the checksum; a correct, unmangled packet sums to all 1s (all-1s, i.e., in ones'-complement). ``` function ONES_COMPLEMENT_SUM(words[1..n]): sum = 0 for i in 1..n: sum = sum + words[i] if sum has a carry out of bit 16: sum = (sum mod 2^16) + 1 // end-around carry return sum mod 2^16 function COMPUTE_CHECKSUM(data_words[1..n]): s = ONES_COMPLEMENT_SUM(data_words) return BITWISE_NOT(s) // 16-bit complement function VERIFY_CHECKSUM(received_words_including_checksum[1..n+1]): s = ONES_COMPLEMENT_SUM(received_words_including_checksum) return (s == 0xFFFF) // all 1-bits means OK ``` This is cheap: pure addition, no multiplication or division, and it can be computed incrementally as packets stream by (useful for routers rewriting only the TTL field, which only needs an incremental update rather than a full recompute). Its weakness is that it is insensitive to reordering of 16-bit words and to certain compensating errors (e.g., incrementing one word while decrementing another leaves the sum unchanged) — it catches typical single-bit noise well but is not a strong adversarial code.
Cyclic Redundancy Check (CRC)
CRC treats an -bit frame as the coefficients of a binary polynomial and a fixed -bit generator polynomial of degree , agreed upon in advance (e.g., CRC-32 uses a specific degree-32 generator). All arithmetic is modulo 2 (XOR, no carries). The sender appends zero bits to , forming , and divides by using binary long division; the -bit remainder becomes the frame check sequence (FCS). The transmitted bit string is
which is constructed precisely so that is exactly divisible by — that is, . ``` function CRC_ENCODE(message_bits, generator_bits): r = length(generator_bits) - 1 dividend = message_bits + ZEROS(r) // append r zero bits remainder = MOD2_DIVIDE(dividend, generator_bits) return message_bits + remainder // transmitted frame function CRC_CHECK(received_frame, generator_bits): remainder = MOD2_DIVIDE(received_frame, generator_bits) return (remainder == 0) // true = accept, false = error function MOD2_DIVIDE(dividend_bits, divisor_bits): r = length(divisor_bits) - 1 rem = dividend_bits[1 .. r+1] // first r+1 bits, as a working register for i in (r+2) .. length(dividend_bits): if rem[1] == 1: rem = (rem XOR divisor_bits)[2 .. end] + dividend_bits[i] else: rem = rem[2 .. end] + dividend_bits[i] // final r-bit shift-and-test after consuming all bits if rem[1] == 1: rem = rem XOR divisor_bits return rem[2 .. end] // r-bit remainder ``` (The pseudocode above sketches the classic shift-register style long division; real implementations use a table-driven or shift-register hardware circuit for speed — see the diagram below.) Because polynomial division over GF(2) is linear, corruption on the wire adds an error polynomial : the received bits are . The receiver's division detects the error exactly when . This single fact is why CRC generators are chosen deliberately:
- If has the factor , all odd-weight errors are caught (an odd number of flipped bits produces , forcing non-divisibility).
- If has degree , all burst errors of length are caught (a burst error polynomial of degree less than , with a leading 1, cannot be divisible by a degree- polynomial unless it is zero).
- Well-chosen generators (CRC-16, CRC-32, CRC-CCITT) also catch the overwhelming majority of longer bursts and of double-bit errors, by design and by exhaustive testing over decades of use.
Forward Error Correction: Hamming codes
Detection alone forces a retransmission when an error is found. Forward error correction adds enough structure that the receiver can locate and flip the bad bit itself. The classical example is the Hamming(7,4) code: 4 data bits protected by 3 parity bits, for a 7-bit codeword, achieving and hence correcting any single-bit error. Parity bits are placed at positions that are powers of two ( at positions 1, 2, 4), and each covers a specific subset of bit positions determined by their binary index: ``` function HAMMING74_ENCODE(d1, d2, d3, d4): // positions: 1 2 3 4 5 6 7 // p1 p2 d1 p4 d2 d3 d4 p1 = d1 XOR d2 XOR d4 p2 = d1 XOR d3 XOR d4 p4 = d2 XOR d3 XOR d4 return [p1, p2, d1, p4, d2, d3, d4] function HAMMING74_DECODE(bits[1..7]): c1 = bits[1] XOR bits[3] XOR bits[5] XOR bits[7] // recompute p1's group c2 = bits[2] XOR bits[3] XOR bits[6] XOR bits[7] // recompute p2's group c4 = bits[4] XOR bits[5] XOR bits[6] XOR bits[7] // recompute p4's group syndrome = c44 + c22 + c1 // binary number = faulty position, 0 = no error if syndrome != 0: bits[syndrome] = NOT bits[syndrome] // flip and correct return [bits[3], bits[5], bits[6], bits[7]] // extract original d1 d2 d3 d4 ``` The syndrome — the vector of parity-check mismatches — is not just "error/no error" but literally encodes the position of the flipped bit in binary. That is the elegant trick: with 3 check bits you can distinguish outcomes (7 possible single-error positions plus "no error"), which is exactly enough, no more, no less — a tight information-theoretic fit. ```svg ```
Complexity & correctness
Parity. Time to compute or verify over data bits; space overhead is a single bit ( extra). Correctness follows directly from the algebra of XOR: parity is the sum mod 2 of all bits, and any single bit flip changes that sum by exactly 1, so it is always caught. Any even number of flips changes the sum by an even number, which is 0 mod 2 — always missed. This is a complete characterization, not a heuristic: exactly, so detect-capability is exactly 1 error, correct-capability is 0.
Internet checksum. Time over 16-bit words, with simple integer addition — no multiplication, dramatically cheaper than CRC in pure software on architectures without CRC instructions. Space overhead is a fixed 16 bits regardless of packet size. It is not a maximum-distance code: it is provably blind to certain reorderings and to compensating additive errors across words, a known and accepted tradeoff for speed given that transport/network layers rely on the stronger link-layer CRC to catch most raw bit errors first, using the checksum mainly as a cheap end-to-end sanity check (e.g., against software or memory bugs, not just link noise).
CRC. Encoding and checking both cost time for an -bit frame with a fixed-degree generator (division is one pass with an -bit shift register, where is a small constant like 32), and space for the shift register — independent of frame length. This is why CRC hardware runs at line rate. Correctness rests on the algebra of GF(2) polynomials:
- Divisibility construction: by design satisfies , because was chosen as exactly that remainder, and adding it cancels the remainder in mod-2 arithmetic.
- Detection condition: the receiver sees (XOR is addition in GF(2), so corruption is additive). Since , we get . The error is detected iff .
- All single-bit errors caught: a single flip gives , a monomial. If has more than one term (true for any real generator), for all , so single flips are always caught.
- All burst errors of length caught: a burst of length can be written with degree . Since has degree exactly , a nonzero polynomial of strictly smaller degree cannot be a multiple of , so unless .
- Undetected error probability for random errors: for a "random" long burst or scattered pattern, roughly of all possible nonzero error polynomials happen to be multiples of and thus slip through — this is why (CRC-32) gives an undetected-error probability near for errors that are not structured bursts, an acceptably tiny risk for most engineering purposes.
Hamming(7,4). Encoding is (3 XORs of fixed-size groups); decoding is (3 XOR checks plus a conditional flip). Space overhead is 3 redundant bits per 4 data bits — a 75% code rate. Correctness is the sphere-packing argument made concrete: with , every pair of valid codewords differs in at least 3 positions, so the "Hamming balls" of radius 1 around each of the 16 codewords (each ball containing the codeword itself plus its 7 single-flip neighbors, words) are disjoint and together cover exactly possible bit strings — a perfect code, using every available 7-bit pattern with zero waste. This is why the syndrome (an integer from 0 to 7) exactly and unambiguously identifies the flipped position: there is no leftover "ambiguous" region, and no wasted encoding capacity.
The general tradeoff. Detection-only schemes (parity, checksum, CRC) are cheap per bit but require a retransmission protocol (e.g., ARQ) when they catch an error, costing at least one round-trip time. FEC schemes (Hamming, Reed–Solomon, LDPC, turbo codes) avoid that round trip by paying redundancy on every frame, whether or not an error occurs — a good trade when round trips are expensive (satellites, deep space, real-time media) or errors are frequent (noisy wireless, flash memory), and a poor trade when the channel is usually clean (a well-shielded fiber, where most frames need no repair and the FEC bits are wasted overhead).
Worked example
Take generator polynomial , i.e., the 4-bit pattern 1011 (this is the standard "CRC-3" style toy example used to hand-trace the algorithm). Suppose the message is . Step 1 — append zero bits: dividend = 1101000. Step 2 — perform mod-2 (XOR) long division by 1011:
| Step | Working register | Action |
|---|---|---|
| 1 | 1101000 | leading bit 1 → XOR with 1011 aligned at top |
| 2 | 1101 XOR 1011 = 0110, bring down → 0110000... | continue shifting |
| 3 | after aligning, leading bit 0 → shift only | |
| 4 | eventually reach remainder | keep dividing until 3 bits remain |
Carrying this out bit by bit (binary long division, XOR instead of subtraction): ``` 1101000 ÷ 1011 1101 1011 ---- 0110 (bring down next bit: 0110 0) 01100 1011 ---- 0111 (bring down next bit: 0111 0... continue) ``` Working through the full division of `1101000` by `1011` yields remainder R = 100. Step 3 — transmitted frame = message + remainder = 1101 100 (7 bits). Step 4 — receiver check: divide `1101100` by `1011`. Because was constructed to be exactly divisible by , this division yields remainder 000, so the frame is accepted. Step 5 — inject a single-bit error: suppose bit 3 flips, giving received frame 1111100. Dividing `1111100` by `1011` now yields a nonzero remainder (the error polynomial is not a multiple of the degree-3 generator, since it is a single nonzero term against a generator with three terms), so the receiver correctly flags corruption and discards or requests retransmission of the frame.
| Quantity | Value |
|---|---|
| Message | 1101 |
| Generator | 1011 (degree ) |
| Padded dividend | 1101000 |
| Remainder | 100 |
| Transmitted | 1101100 |
| Clean receive check | remainder = 000 → accept |
| Corrupted receive (bit 3 flipped) | remainder ≠ 000 → reject |
Common pitfalls & traps
| Pitfall | Why it happens | Fix |
|---|---|---|
| Assuming checksum ⇒ correction | Confusing detection with correction | Only FEC codes (Hamming, Reed–Solomon) repair; checksums/CRC only detect and must trigger retransmission |
| Using CRC generator with even weight only | Some generators fail to catch all odd-weight errors | Always include factor in the generator to guarantee odd-error detection |
| Off-by-one in appended zero bits | Appending bits instead of exactly matching generator degree, or vice versa | Append exactly zero bits before dividing |
| Treating checksum as cryptographically secure | Checksums/CRCs detect accidental noise, not adversarial tampering | Use MACs/digital signatures for security; CRC/checksum for transmission noise only |
| Forgetting that parity misses even-count errors | Two simultaneous flips restore parity | Use 2D parity, CRC, or Hamming codes when burst/multi-bit errors are likely |
| Ignoring FEC's fixed bandwidth tax | FEC spends redundancy on every frame, clean or not | Reserve FEC for channels with frequent errors or costly retransmission (satellite, deep space, flash storage) |
Pioneers & history
Origins · 1 minRichard Wesley Hamming and how this was discovered
Hamming worked at Bell Labs in the late 1940s on early relay computers that used punched-card input, batch-processed overnight; a single undetected error would silently ruin a run and waste an entire weekend. Frustrated that existing parity checks could only detect, never correct, errors, he devised the first practical family of error-correcting codes, published in 1950. His work founded coding theory as a rigorous mathematical discipline and directly inspired the CRC and Reed–Solomon codes used in every modern network and storage device.
Where this matters
Ethernet and Wi-Fi append a 32-bit CRC (the Frame Check Sequence) to every frame, computed and checked in hardware at line rate — the topic that this discussion of framing directly sets up, since a frame is only as reliable as its error-detection field, explored further in Ethernet and the Frame Format. TCP, UDP, and IP carry the 16-bit Internet checksum in every header and (for UDP/TCP) over the payload, a lightweight end-to-end sanity check layered on top of link-layer CRCs. RAID arrays and ECC (error-correcting code) RAM use parity and Hamming-style codes to survive disk or memory-cell failures without data loss, silently correcting single-bit memory errors that occur constantly at scale in data centers. Storage media — CDs, DVDs, Blu-ray, SSD flash controllers, and QR codes — rely on Reed–Solomon codes to survive scratches, dust, and flash-cell wear. Deep-space communication, from the Voyager probes to modern Mars rovers, depends on powerful FEC (concatenated Reed–Solomon/convolutional codes, and now LDPC and turbo codes) because a retransmission request can take hours to reach a probe billions of kilometers away, making forward correction the only economical option. Anywhere data must survive a noisy channel or storage medium without a cheap opportunity to ask "please resend," some descendant of parity, CRC, or Hamming coding is quietly at work.
Ask the tutor: How does a router recompute an IP checksum incrementally when it only changes the TTL field, without rescanning the whole header?
Why does CRC use polynomial division instead of simple addition like a checksum?
Polynomial division over GF(2) has a property plain addition lacks: it is exquisitely sensitive to the position of errors, not just their count. A checksum's simple sum can be fooled by errors that cancel out numerically (one word too high, another too low). CRC's remainder depends on exactly where each bit sits within the polynomial, because shifting a bit changes which power of it multiplies. This lets CRC designers mathematically guarantee catching every burst error up to the generator's degree and all odd-weight errors — guarantees a simple additive checksum cannot offer. The cost is a division operation instead of addition, but dedicated shift-register hardware makes this essentially free at line rate, which is why CRC dominates hardware-checked link-layer frames while checksums remain popular in software-checked transport headers.
Can a CRC or checksum ever miss an error, and how likely is that?
Yes — both are detection schemes with mathematically defined blind spots, not perfect oracles. A checksum can miss compensating errors across different words. A CRC misses any error pattern whose polynomial happens to be an exact multiple of the generator polynomial; for CRC-32, roughly 1 in 4 billion () random error patterns that are not simple bursts or odd-weight patterns will slip through undetected. This is why CRCs are chosen with generators empirically tested against real-world burst and multi-bit error statistics from actual links, not just theoretical worst cases, and why critical systems layer multiple checks (link-layer CRC plus transport checksum plus, for security, cryptographic MACs) rather than relying on any single check alone.
What is the difference between error detection and forward error correction in practical system design?
Detection-only schemes (parity, checksum, CRC) tell the receiver "something is wrong" and rely on a separate protocol, usually automatic repeat request (ARQ), to fetch a clean copy by retransmission. Forward error correction (FEC) embeds enough redundancy that the receiver reconstructs the correct data immediately, without any round trip back to the sender. The tradeoff is bandwidth versus latency: FEC spends extra bits on every single frame, whether or not it is ever corrupted, while detection-plus-retransmission spends bits only when errors occur but pays a round-trip-time penalty when they do. Systems choose based on error frequency and the cost of a round trip — frequent errors or expensive round trips favor FEC; rare errors on a link with cheap, fast retransmission favor detection alone.
Why does the Hamming(7,4) code place parity bits at positions 1, 2, and 4 instead of just appending them at the end?
Placing parity bits at power-of-two positions (1, 2, 4, 8, …) is what makes the syndrome decoding trick work. Each parity bit's binary index determines exactly which data positions it covers: parity bit at position covers every position whose binary representation has a 1 in bit . When the receiver recomputes all parity checks and stacks the mismatch bits together, the resulting binary number is not an arbitrary code — it is literally the index of the flawed position, because the placement was engineered so each bit position has a unique "fingerprint" of which parity groups include it. Appending parity bits at the end would lose this direct positional encoding and require a separate lookup table instead of a simple binary readout.
Does adding more redundant bits always improve error resilience?
Not for free, and not without limit — there is a real information-theoretic tradeoff. More redundant bits generally increase minimum Hamming distance, improving detection and correction power, but they also increase transmission overhead (lower code rate) and, for very long codes, encoding/decoding complexity. Shannon's channel capacity theorem sets a hard ceiling: below capacity, arbitrarily reliable transmission is theoretically possible with a sufficiently clever code, but above capacity no amount of redundancy helps, because errors are being introduced faster than any code can compensate. Real system design balances desired reliability, available bandwidth, and decoder complexity — hence lightweight parity for RAM, mid-weight CRC-32 for Ethernet frames, and heavyweight Reed–Solomon or LDPC codes only where channels are especially harsh or retransmission is especially costly.
Is the Internet checksum obsolete now that CRC and stronger codes exist?
It persists because it serves a different purpose than link-layer CRC, not because it is the strongest available code. Link-layer CRC already catches the overwhelming majority of physical transmission errors before a frame is even accepted by a switch or network interface card. The Internet checksum in IP/TCP/UDP headers is a lightweight, software-computable, end-to-end sanity check that also catches errors CRC cannot see — bugs in routers, memory corruption inside a host, or bit rot introduced after the link-layer check already passed. It survives because it is cheap to compute in software across many architectures and layers, complementing rather than duplicating the stronger, hardware-accelerated CRC done at the link layer.