Checksums
Checksums for system design interviews: what a checksum detects, the Internet checksum worked by hand, CRC32C vs SHA-256 vs HMAC for corruption vs tampering, where checksums live (TCP, Ethernet, Kafka, HDFS, S3, database pages), the end-to-end argument, and chunk hashing for Dropbox-style upload and dedup.
On this page
🧮 What is a checksum?
A checksum is a small value computed from a block of data and stored or sent alongside it. Whoever reads the data later recomputes the value; if the two disagree, the data changed somewhere along the way. It works like the last digit of a credit card number: one extra digit catches most typos, but anyone who knows the formula can make a fake number that passes.
For this lesson, we'll cover what checksums can and can't detect, work through the Internet checksum by hand, compare it with CRCs, cryptographic hashes and HMACs, and map where each one shows up in real systems, from TCP headers to S3 uploads to Dropbox-style chunking. This is a "good to know" topic: it rarely gets its own question, but it decides whether your answer to "how do you know the file you stored is the file the user uploaded?" sounds hand-wavy or precise.
Below the line (out of scope)
- Signing webhooks and API requests in depth: Webhooks, Authentication
- Encryption and keys (a different job from integrity checks): Encryption and key management
- The transports that carry a checksum in every packet: TCP vs UDP
The 30-second answer
A checksum detects accidental change: a flipped bit on a wire, a bad disk sector, a buggy copy. It can't stop deliberate change, because whoever alters the data can recompute it. The Internet checksum in IP, TCP and UDP is a 16-bit ones'-complement sum: cheap but weak. A CRC such as CRC32C is far better at random and burst errors and is used in Ethernet, Kafka and object stores. A cryptographic hash like SHA-256 makes collisions practically impossible, so it doubles as a content address for deduplication. To detect tampering you need a secret: an HMAC, or a digital signature. In a design, I checksum end to end: compute it where the data is born, verify it where it's finally used, because every lower layer only covers its own hop.
What you’ll learn
- Explain what a checksum detects (accidental change) and what it can't (deliberate tampering)
- Compute the Internet checksum by hand and show the errors it misses
- Choose between CRC32C, SHA-256 and HMAC based on the threat
- Map where checksums live: Ethernet, TCP/UDP, Kafka, HDFS, database pages, S3 and GCS
- Apply the end-to-end argument: compute at the source, verify at final use
- Use chunk hashes for upload, dedup and integrity in a Dropbox-style design
01Picture it first: compute, send, recompute
Every scheme on this page follows the same four steps; they differ only in the function f and who's allowed to compute it.
Compute, send, recompute, compare
Notice three things:
- Detection, not correction. A checksum tells you that something changed, not what. Recovery is a separate step: retransmit the packet, read another replica, restore a backup.
- Match means "probably unchanged". A 16-bit checksum has only 65,536 possible values, so some corruptions will land on the same value. Bigger and better-mixed functions make that vanishingly unlikely.
- No secret, no security. If the attacker can change the bytes, they can change the checksum. That's the whole difference between a checksum and a MAC.
02The Internet checksum, by hand
IP, TCP and UDP all use the same function, defined in RFC 1071: treat the data as 16-bit words, add them with ones'-complement arithmetic (any carry out of the top bit is wrapped around and added back at the bottom), then flip every bit of the result. The receiver adds up everything including the checksum, and a correct packet sums to 0xFFFF.
Try it yourself first
Before reading on, compute it yourself. The data is three 16-bit words: 0x8F2A, 0x9B11, 0x1234. Add them with end-around carry, then invert. Then check two things: does flipping the last bit of the first word get caught? Does swapping the first two words get caught?
Here's my working:
0x8F2A
+ 0x9B11
= 0x12A3B ← carry out of 16 bits
→ 0x2A3B + 0x1 = 0x2A3C (end-around carry)
+ 0x1234
= 0x3C70
checksum = ~0x3C70 = 0xC38F (sent with the data)
Receiver, data intact: 0x3C70 + 0xC38F = 0xFFFF ✓ accept
One bit flipped (0x8F2B): 0x3C71 + 0xC38F = 0x10000 → 0x0001 ✗ caught
Words 1 and 2 swapped: same sum, 0xFFFF ✓ accepted: missed!
+1 in word 1, -1 in word 2: same sum, 0xFFFF ✓ accepted: missed!
That's the design trade-off in four lines. The Internet checksum is a sum, so it can't see reordering and it misses errors that cancel out. It was chosen because it's cheap to compute in software and can be updated incrementally when a router changes one header field, not because it's strong.
How weak is it in practice? Stone and Partridge traced real Internet traffic and found that between 1 packet in 1,100 and 1 in 32,000 failed the TCP checksum, even on links whose CRCs should have caught all but 1 in 4 billion errors. The corruption was happening inside hosts and routers, from memory errors to software bugs, after the link CRC had been checked. Their estimate: the TCP checksum lets an error through somewhere between 1 in 16 million and 1 in 10 billion packets. At millions of packets a second, "rare" happens daily, which is why serious systems add their own checks on top.
03CRCs, hashes and MACs: picking the right tool
The four families answer different questions. The table is what I'd draw if an interviewer asks "would a checksum stop that?"
| Internet checksum | CRC32 / CRC32C | SHA-256 | HMAC-SHA256 | |
|---|---|---|---|---|
| Size | 16 bits | 32 bits | 256 bits | 256 bits |
| Built from | Ones'-complement sum | Polynomial division | Cryptographic hash | Hash + secret key (RFC 2104) |
| Random bit errors | Catches many, not all | Excellent; every burst up to 32 bits | Essentially all | Essentially all |
| Reordered data | Misses it | Catches it | Catches it | Catches it |
| Deliberate tampering | No | No | Only if the hash comes over a trusted channel | Yes, without the key you can't forge it |
| Cost | Cheapest | Very cheap; CPU instructions for CRC32C | Moderate | Moderate (two hash passes) |
| Where you see it | IP, TCP, UDP headers | Ethernet frames, Kafka batches, GCS, S3, iSCSI | Dropbox blocks, S3 checksums, downloads, content addressing | Webhook signatures, AWS request signing, session tokens |
A few lines on each:
- CRC (cyclic redundancy check). Treats the data as a big polynomial and keeps the remainder after dividing by a fixed one. It's built for the errors hardware actually makes: bursts of flipped bits. CRC32C uses the Castagnoli polynomial, which modern x86 and ARM CPUs can compute with dedicated instructions, so it costs almost nothing. Java ships it as
java.util.zip.CRC32Csince Java 9. - Cryptographic hash (SHA-256). Designed so nobody can find two inputs with the same output. That makes the hash a safe name for the content, which is what deduplication relies on. MD5 and SHA-1 are still fine for catching accidental corruption but broken for anything adversarial.
- HMAC. A hash mixed with a secret key. Only someone holding the key can produce a matching tag, so it proves integrity and origin. A digital signature does the same with a public/private key pair, so anyone can verify without being able to sign.
Many candidates say "we'll add a checksum so nobody can tamper with the payload". A checksum, or even a plain SHA-256 sent alongside the data, protects against nothing an attacker does, because they rewrite the hash too. If the threat is a person, say HMAC with a shared secret (webhooks, internal requests) or a signature (software updates, JWTs signed with RS256 or ES256). If the threat is cosmic rays and bad disks, a CRC32C is enough and much cheaper.
04In Java
Here's what each of those looks like in code. Each is a few lines; the hard part is deciding which one the threat calls for.
// Accidental corruption: CRC32C (Java 9+), cheap enough for every block
CRC32C crc = new CRC32C();
crc.update(block, 0, block.length);
long crc32c = crc.getValue();
// Content identity / dedup: SHA-256
byte[] sha256 = MessageDigest.getInstance("SHA-256").digest(block);
// Tampering: HMAC-SHA256 with a shared secret
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret, "HmacSHA256"));
byte[] expected = mac.doFinal(body);
boolean authentic = MessageDigest.isEqual(expected, receivedTag); // constant-time compare
Notice the last line. Comparing MACs with Arrays.equals can leak, through timing, how many leading bytes matched; MessageDigest.isEqual doesn't stop early.
05Where checksums live in a system
Every layer checks its own piece of the path, and each one stops at its boundary. This figure is the reason "TCP already has a checksum" isn't an answer.
Every hop checks its own segment; only an end-to-end check covers the gaps
| Layer | Check | What it covers |
|---|---|---|
| Ethernet | Frame check sequence, a 32-bit CRC | One link. Each switch or router verifies it, then computes a fresh one for the next link |
| IP / TCP / UDP | 16-bit Internet checksum | Sending host to receiving host (UDP's is optional over IPv4, required over IPv6) |
| Kafka | CRC-32C per record batch | Producer to consumer, including the copy in the broker's log on disk |
| HDFS | Checksums per block, stored in a separate hidden file | Reads verify them; on a mismatch the client reads another replica |
| Database pages | Page checksum (PostgreSQL's current docs have it on by default) | Written with the page, verified each time it's read from disk |
| Object stores | S3: CRC64NVME (default), CRC32, CRC32C, SHA-1, SHA-256, MD5 via x-amz-checksum-* or legacy Content-MD5. GCS: CRC32C (recommended) and MD5 |
Client to stored object; S3 computes its own value and validates it against yours before storing |
| Your application | CRC32C or SHA-256 you compute | Wherever you choose: ideally from creation to final use |
The end-to-end argument
This pattern has a name. Saltzer, Reed and Clark's 1984 paper on the end-to-end argument uses careful file transfer as its example: however reliable each step is, only a check by the endpoints, comparing what was written with what was read, can guarantee the file arrived intact. Lower-layer checks are still worth having because they catch most errors early and cheaply, but they're a performance optimisation, not the guarantee.
Meta described a silent data corruption they traced to one faulty CPU core. In a large compressed-file pipeline, a size calculation sometimes returned 0 for a non-zero file, so the decompression step skipped the file and rows went missing from a downstream database, with nothing raising an error. After shrinking the problem to a single machine, the team found that on core 59 a Scala math.pow computation, Int(1.1^53), returned 0 instead of 156, reproducibly for certain inputs. No network or disk checksum could have caught it: the bytes were computed wrong before any checksum was taken. Meta's conclusion was that hardware fixes aren't enough and software needs its own fault-tolerant checks. The lesson I take into designs: verify at the point of use, and compare results across replicas when correctness really matters.
06How it shows up in design problems
You'll rarely be asked "explain CRC32". You'll be asked about uploads, storage and messaging, and a checksum is the precise answer to the integrity follow-up.
File upload, sync and dedup
In Design Dropbox / Google Drive, the standard move is to split files into fixed-size chunks and name each chunk by its hash. Dropbox's engineering blog (Streaming file synchronization) describes exactly this: files split into 4 MB blocks, each hashed with SHA-256, and a file described by its list of block hashes.
Content-addressed chunks: hashes find what to upload and prove it arrived
The client sends the hash list first, the server answers with the hashes it doesn't have, and only those blocks travel. Unchanged blocks of an edited file, and files another user already uploaded, cost nothing. Because the block's name is its hash, the store verifies every block on arrival for free. Two cautions: cross-user dedup can leak whether a file already exists (an attacker uploads a guess and watches whether it transfers), and a change near the start of a file shifts every fixed-size block after it, which content-defined chunking fixes at some complexity.
Other problems
- Design an object store like S3: client-supplied checksums on upload, stored with the object, re-verified on read and by a background scrubber that repairs from another replica.
- Design a distributed message queue (Kafka): a CRC per batch, set by the producer and checked by the consumer, so a bad disk on one broker can't silently corrupt messages.
- Design a webhook delivery service: an HMAC signature over the body plus a timestamp, so receivers can reject forged or replayed calls. That's integrity and authenticity, not a checksum.
- Design a key-value store: Merkle trees (hashes of hashes) let two replicas find which key ranges differ without comparing every value.
At this point, you should ask your interviewer what you're defending against. "Bit rot and bad disks" leads to CRC32C and scrubbing; "a malicious client or a man in the middle" leads to HMACs, signatures and TLS.
07Trade-offs and failure modes
| Failure | Why it happens | Better default |
|---|---|---|
| Corruption passes every check | It happened in memory or CPU between layer checks | Checksum at creation, verify at final use |
| Corrupt replica keeps serving | Nobody reads cold data, so nobody notices | Background scrubbing that verifies and repairs |
| "Signed" payload was forged | Plain hash or CRC used against an attacker | HMAC or signature, constant-time compare |
| Two different files deduplicated | Weak or short hash used as a content key | SHA-256 for content addressing, never CRC32 |
| Checksum computed after corruption | Calculated on the server after a buggy transform | Compute on the client, send it with the upload |
| Too slow on the hot path | SHA-256 on every small message | CRC32C for integrity; hash only where identity matters |
08Follow-up questions
What if the interviewer asks: "TCP already has a checksum, why add another?"
TCP's 16-bit checksum covers only the hop between two hosts' network stacks, it misses reordered and cancelling errors, and it says nothing about data sitting in memory, on disk, or passing through proxies that re-send it on a new connection. Measurements of real traffic found errors slipping past it at rates you hit every day at scale. An application checksum covers the whole path.
What if two different chunks have the same hash in a dedup system?
With SHA-256, finding a collision isn't practically possible, so systems treat the hash as the identity. With CRC32 it would happen by chance after tens of thousands of chunks, which is why CRC is used to detect corruption, never to name content. If someone insists, you can compare the bytes on a hash match, at the cost of reading the stored chunk.
What if a disk silently corrupts a block that nobody reads for a year?
Then the corruption is discovered only when a user asks, and by then the other replicas may have failed too. Store a checksum with every block, verify on every read, and run a background scrubber that reads cold data on a schedule and repairs bad copies from healthy replicas.
What if an attacker can modify messages in your internal queue?
Then CRCs don't help; they'll recompute them. Sign messages with an HMAC using a key the attacker doesn't have, or a signature if many consumers need to verify without being able to sign, and protect the transport with TLS. Rotate keys and include a key ID so you can.
09How to say it in the interview
When I ask "how do you know the data wasn't corrupted?", I'm listening for three things: that you separate accidental corruption from tampering, that you pick a function to match, and that you check end to end rather than trusting each layer.
In the interview
"I'd separate two threats. For accidental corruption, bad disks, flaky NICs, buggy hops, I'd use CRC32C: it's cheap, has hardware support and is much stronger than the 16-bit Internet checksum that TCP uses, which can't even see reordered words. For identity and dedup, I'd use SHA-256, so a chunk's hash is its name, the upload protocol only sends missing chunks, and the store verifies every chunk on arrival. For tampering, a checksum does nothing because the attacker recomputes it, so webhooks and internal messages get an HMAC with a shared secret, compared in constant time. And I'd apply the end-to-end argument: the client computes the checksum before upload, the object store verifies it, and we verify again on read and with a background scrubber, because each lower-layer check stops at its own boundary and real corruption, like Meta's faulty CPU core, happens in between."
On the whiteboard
- Checksum = detect accidental change · match = probably OK · mismatch = proof
- Internet checksum (RFC 1071): 16-bit ones'-complement sum · misses reordering + cancelling errors
- CRC32C: bursts ≤ 32 bits, HW-fast · Ethernet FCS, Kafka, GCS, S3
- SHA-256: collision-resistant → content address, dedup (Dropbox 4 MB blocks)
- Tampering → HMAC (RFC 2104) or signature · constant-time compare
- Each layer covers its hop only · end-to-end: compute at source, verify at use
- Scrub cold data · repair from replicas
Further reading (6)
- RFC 1071: Computing the Internet Checksum: The ones'-complement sum used by IP, TCP and UDP, and why it was chosen: cheap and incrementally updatable.
- RFC 2104: HMAC, keyed-hashing for message authentication: How a secret key turns a hash into a tag that only key holders can produce.
- When the CRC and TCP checksum disagree (Stone and Partridge, SIGCOMM 2000): Measured how often real packets arrive corrupted despite link CRCs, and how many errors slip past TCP's checksum.
- End-to-end arguments in system design (Saltzer, Reed and Clark): The classic paper on why only endpoint checks can guarantee correctness, using careful file transfer as the example.
- Checking object integrity in Amazon S3 (AWS docs): The checksum algorithms S3 supports, the CRC64NVME default, and server-side validation of uploads.
- Silent data corruption: mitigating effects at scale (Engineering at Meta): A faulty CPU core silently producing wrong results and missing database rows, traced down to one instruction path.