Networking and API Design for System Design

Checksums

Data gets corrupted in transit and at rest. Checksums are how systems detect that corruption. Understanding them matters for reliability and data integrity design.


The Concept Explained

A checksum is a small value computed from a block of data such that changing the data almost certainly changes the value. Send both, recompute on arrival, compare. A mismatch means something changed.

The reason this matters in system design is a fact people underrate: data corruption is not rare at scale. Bits flip in memory, on disks, in network equipment, and in the software handling them. At one machine it is a curiosity. Across thousands of machines moving petabytes, silent corruption is a steady background event, and the only question is whether you detect it or serve it to a customer.

KEY CONCEPT

The failure mode a checksum protects against is not loss, it is silent corruption. Lost data announces itself: a timeout, a connection reset, a missing file. Corrupted data does not. It flows through your system as a perfectly valid-looking value, gets written to the database, propagates to replicas and backups, and is discovered months later when the numbers do not add up. Detection has to happen at write time, because after that it looks like truth.


How It Works

The layered checks that already exist

Corruption detection is not something you add from nothing. Several layers already do it, with meaningfully different strength:

LayerMechanismStrength
Ethernet frameCRC32Strong for burst errors on a link
IP header16-bit ones-complementHeader only, weak
TCP segment16-bit ones-complementWeak by modern standards
TLS recordAEAD authentication tagCryptographically strong
ApplicationWhatever you chooseYour decision
StorageFilesystem or device checksumsVaries by system

The row to notice is TCP. Its checksum is 16 bits and simple, designed in an era of different hardware assumptions. It catches most random corruption and misses a meaningful fraction of certain error patterns. Ethernet's CRC is stronger, and it only protects a single link: a packet is checked and re-checked hop by hop, so corruption occurring inside a router's memory, between receiving and re-transmitting, is not covered by any link-level CRC and may slip past TCP's weak check.

This is not theoretical. Studies of large-scale data transfer have found error rates where TCP-verified data still arrived corrupted, at rates that matter when you move enough of it.

The practical conclusion: TCP's checksum is a filter, not a guarantee. If your data must be correct, verify it yourself.

The end-to-end argument

This is the principle to name in an interview, because it explains where verification belongs.

The end-to-end argument, from Saltzer, Reed, and Clark, says a function like integrity checking can only be implemented completely at the endpoints of a communication, since only the endpoints see the whole picture. Checks at lower layers are useful performance optimizations (catching errors early avoids transferring bad data further), and they cannot substitute for the end-to-end check.

Applied concretely:

client                                                        server
  │ compute hash of the payload                                 │
  ├──── send payload + hash ───[TCP checks]──[TLS checks]──────>│
  │                                                    recompute hash
  │                                                    compare ──> accept or reject

Every intermediate check can pass while the data is still wrong, because corruption can occur in a place no intermediate check covers: application memory before transmission, a proxy's buffer, a storage layer after receipt. Only the endpoints can verify the thing they actually care about.

Integrity versus security

This distinction is frequently muddled and interviewers do probe it.

A plain checksum or hash detects accidental corruption. It does not stop deliberate tampering, because an attacker who modifies the data simply recomputes the checksum and sends both.

Accidental corruption      CRC32, MD5, SHA-256 of the content        sufficient
Deliberate tampering       HMAC (hash + secret), or a digital signature

To resist tampering you need something the attacker cannot compute: a keyed hash (HMAC) or a signature over the content. That is exactly why webhook signatures are HMACs rather than hashes (Lesson 5.4), and why a JWT's signature, not a checksum, is what makes its claims trustworthy (Lesson 4.3).

A related nuance worth getting right: MD5 and SHA-1 are broken for collision resistance, meaning an attacker can construct two different inputs with the same digest. They remain perfectly adequate for detecting accidental corruption, which is why you still see MD5 in storage systems. Using them for signatures or deduplication of untrusted content is where it becomes a vulnerability. Being precise about "broken for what" is a better answer than "MD5 is insecure."

Where this appears in systems you design

Object storage. S3 accepts a Content-MD5 on upload and rejects the request on mismatch, and returns ETag values clients use to verify. Verifying at upload converts a silent bad object into a failed request, which is exactly the trade you want.

Large file transfer, the interview question at the end of this lesson. The pattern:

1. Split the file into chunks (say 8MB)
2. Checksum each chunk; upload chunks in parallel
3. Server verifies each chunk on arrival, rejects and re-requests failures
4. Checksum the whole file; verify after reassembly
5. Resume by re-sending only unverified chunks

Chunk checksums bound the cost of a failure: a corrupt chunk costs 8MB of retransmission rather than the whole file, and it is what makes resumable uploads possible. The whole-file checksum catches errors in reassembly itself, which chunk checks cannot see.

Replication and anti-entropy. How do two replicas confirm they hold identical data without transferring all of it? Merkle trees: hash each block, hash pairs of hashes upward to a single root. Equal roots mean equal data. Different roots let you descend the tree and find exactly which blocks differ, in logarithmic rather than linear comparisons. This is how Dynamo-style systems (Cassandra, Riak) repair replicas, and it is a genuinely strong thing to mention in a distributed systems discussion.

Content addressing. Use the hash of the content as its identifier. Git does this for every object, and container registries do it for image layers (which is why a digest pins an image far more reliably than a tag). Two useful properties follow for free: identical content deduplicates automatically, and the identifier is self-verifying, since you can always recompute the hash and confirm you got what you asked for.

Idempotency by content hash. Where a client cannot supply an idempotency key, hashing the request body gives a natural deduplication key (Lesson 2.2). It works when identical content genuinely means a duplicate, and fails when two legitimate identical requests should both be processed, so it is a narrower tool than an explicit key.

The cost

Hashing is not free, and picking the algorithm on that basis is reasonable:

  • CRC32 is extremely fast, often hardware-accelerated, and adequate for accidental corruption. The right choice for per-chunk checks at high throughput.
  • SHA-256 is slower and cryptographically strong, and modern CPUs accelerate it. The default when the value may be used for identity or integrity claims.
  • Verification cost is paid on every read if you check on read as well as write, which is a real throughput consideration for storage systems.

Filesystems like ZFS checksum every block and verify on every read specifically because they treat silent corruption as expected rather than exceptional, and they accept that CPU cost deliberately.


System Design Implications

  • Verify at the boundary where you take responsibility. When you accept an upload, verify before acknowledging. An acknowledged corrupt object is now your corrupt object.
  • Silent corruption is the failure to design against. Loss is loud and easy; corruption is quiet and propagates into replicas and backups before anyone notices.
  • Checksums make retries targeted. Knowing which chunk failed means resending kilobytes instead of gigabytes, which is what makes resumable transfer work.
  • Content addressing gives deduplication and verification together, and it is why registries and version control systems are built on hashes.
  • Do not confuse a checksum with a signature. If the threat is an adversary rather than a cosmic ray, you need a key in the computation.

Tradeoffs and Decision Framework

Strength versus speed. CRC32 for high-volume accidental-error detection; SHA-256 when the digest carries any trust or identity meaning.

Where to verify. End-to-end verification is the one that counts. Intermediate checks are optimizations that fail fast and cheaply.

Verify on write versus on read. Write-time verification catches corruption at ingestion. Read-time verification also catches corruption that happened at rest, at ongoing CPU cost. Storage systems that care do both.

Chunk size. Smaller chunks mean cheaper retries and more checksum overhead and more round trips. 8-16MB is a common balance for large uploads.

Checksum versus HMAC versus signature. Accidental corruption, tampering by someone without a shared secret, or verifiable authorship. Pick by threat, not by habit.


Common Mistakes

Assuming TCP guarantees integrity. A weak 16-bit check that misses a real fraction of corruption, and does not cover corruption inside intermediaries.

Using a plain hash where tampering is the threat. An attacker recomputes it.

Calling MD5 unusable. Broken for collision resistance, still fine for accidental corruption detection. Say which property you mean.

Verifying only after full reassembly. Wastes an entire transfer on one bad chunk and gives no resume path.

Never verifying at rest. Corruption that occurs after a successful write goes undetected until read, and by then it may be in every backup.

Not verifying before acknowledging an upload. You have taken ownership of data you never confirmed.

Ignoring the CPU cost at scale. Hashing every block on every read is a deliberate throughput trade, not a free feature.


INTERVIEW QUESTION

How would you ensure data integrity when transferring large files across an unreliable network?