skip to content

How does TCP use sequence numbers and acknowledgment numbers to turn unreliable IP delivery into a reliable, ordered byte stream?

level: juniorimportance: must knowfreq 62%

answer

  1. number the data, not the packets
  2. the ACK names what comes next
  3. one ACK covers everything below it
  4. receiver reorders, sender keeps until covered

basics

~20 s

TCP numbers every byte it sends. The receiver's acknowledgment number names the next byte it expects, confirming all earlier bytes at once; sequence numbers let the receiver reorder and drop duplicates, and the sender resends whatever stays unacknowledged.

solid answer

~50 s

Every byte on a TCP connection has a 32-bit sequence number, and a segment's `Sequence Number` field carries the number of its first data byte (SYN and FIN each occupy one number as well). The receiver replies with an `Acknowledgment Number` equal to the next byte it expects, and that acknowledgment is **cumulative**: ACK 5001 says every byte up to 5000 has arrived, however many segments carried it. Because data is identified by its position in the stream rather than by packet, the receiver can slot a late segment back into place, discard bytes it already holds, and hand the application a gap-free stream in order. The sender keeps a copy of every byte until a cumulative ACK moves past it and retransmits what stays uncovered; when it does so is the business of the retransmission timer and fast retransmit.

go deeper

for a junior

Recall that TCP numbers bytes, that the ACK number is the next byte expected, and that it confirms everything before it in one go.

for a middle

Explain the receiver's steps for each segment, why out-of-order data is buffered rather than delivered, and why SYN and FIN occupy sequence numbers.

for a senior

Connect cumulative ACKs to recovery behaviour: one hole blocks the whole stream for the reader, and the sender only learns about bytes past the hole from duplicate ACKs or SACK.

for a principal

Be ready to weigh a reliable ordered byte stream against message-oriented transports, where one lost packet need not stall unrelated data behind it.

## The problem TCP is solving IP delivers **datagrams** on a best-effort basis: a packet can be lost, duplicated, delayed or overtaken by a later one, and IP will not tell anyone. Applications, on the other hand, want to write a stream of bytes at one end and read exactly the same bytes, in the same order, at the other. TCP (specified today by **RFC 9293**, which obsoleted RFC 793 in 2022) closes that gap with two numbers carried in every segment: the **sequence number** and the **acknowledgment number**. ## Numbering bytes, not packets RFC 9293 puts it plainly: every octet of data sent over a TCP connection has a sequence number, and because every octet is sequenced, each of them can be acknowledged. - The `Sequence Number` field of a segment holds the number of the **first data byte** in that segment; the following bytes are numbered consecutively. - A segment carrying 1,000 bytes that starts at 5001 therefore covers bytes 5001 to 6000, and the next new byte the sender will use is 6001. - Two control flags also occupy sequence space: **SYN** takes one number before the first data byte, and **FIN** takes one number after the last. A segment that carries only an acknowledgment consumes none. - The starting value, the **initial sequence number (ISN)**, is chosen per connection during the handshake; how it is chosen belongs to connection establishment, not to this topic. Numbering bytes rather than segments matters because segment boundaries mean nothing to the application. A sender may combine or split data differently when it retransmits, and any acknowledgment still makes sense because it refers to positions in the stream. ## The cumulative acknowledgment The receiver answers with an `Acknowledgment Number` (valid when the ACK flag is set) equal to the **next sequence number it expects**. The mechanism is **cumulative**: an acknowledgment of X means all bytes up to, but not including, X have arrived. | Side | Variable (RFC 9293) | Meaning | |---|---|---| | Sender | `SND.UNA` | oldest byte sent but not yet acknowledged | | Sender | `SND.NXT` | next byte the sender will send | | Receiver | `RCV.NXT` | next byte the receiver expects; the value it acknowledges | | Receiver | `RCV.WND` | how far beyond `RCV.NXT` it will accept data | An incoming acknowledgment is new (an "acceptable ack") when `SND.UNA < SEG.ACK =< SND.NXT`; the sender then advances `SND.UNA` and removes fully acknowledged segments from its retransmission queue. ## What the receiver does with each arriving segment 1. It checks that the segment's bytes fall inside its receive window, starting at `RCV.NXT`. 2. It trims anything it has already received, which is how **duplicates** from retransmission or from the network disappear. 3. If the segment starts exactly at `RCV.NXT`, the bytes join the in-order stream and `RCV.NXT` advances. 4. If it starts beyond `RCV.NXT`, there is a gap; RFC 9293 says such segments SHOULD be held for later processing, so the receiver buffers them without delivering them. 5. It sends an acknowledgment carrying the new `RCV.NXT`. Because acknowledgments are cumulative, one ACK may cover several segments (delayed acknowledgment is a separate subject), while a segment that arrives above a gap SHOULD be acknowledged immediately (RFC 5681). The application reads only from the contiguous prefix, so it never sees a hole or a reordering. ## What the sender keeps The sender holds every transmitted byte until a cumulative acknowledgment moves past it. Bytes that stay unacknowledged are eventually sent again, triggered for example by the retransmission timer or by duplicate acknowledgments; those triggers and their timing are owned by the retransmission and congestion-control topics. Even when the optional **SACK** extension (RFC 2018) reports that later bytes arrived, the sender must keep them until the cumulative acknowledgment covers them, because the receiver is allowed to discard SACKed data. ## A finite number space Sequence numbers are 32 bits wide, so they run from 0 to 2^32 - 1 and wrap around. RFC 9293 requires all comparisons to be done **modulo 2^32**. On very fast connections the space can wrap while old duplicates are still in the network; RFC 7323's **PAWS** mechanism uses the timestamp option to reject such stale segments. ## Common misconceptions - "ACK 5 means the fifth packet arrived": acknowledgment numbers name bytes, not packets. - "An ACK confirms only the segment it replies to": it confirms the whole prefix below it. - "IP keeps packets in order, so sequence numbers only detect loss": IP makes no ordering promise, and the receiver reorders using the sequence numbers.

  • Why does TCP acknowledge bytes rather than segments?
    Segment boundaries are an accident of transmission, not part of the application's data. A sender may repacketize when it retransmits, merging two small segments or splitting a large one, and a byte-based acknowledgment still reads correctly because it names a position in the stream. RFC 9293 ties this to duplicate detection: since every octet is sequenced, the receiver can recognise bytes it already holds no matter how they were packaged.
  • Does a TCP receiver have to send one ACK for every segment it receives?
    No. Because acknowledgments are cumulative, one ACK can confirm several segments, and RFC 9293 recommends delayed acknowledgment, bounded so that an ACK is still sent for at least every second full-sized segment. The exception is data arriving above a gap: RFC 5681 says the receiver SHOULD acknowledge it immediately, because that duplicate ACK is the sender's early sign of trouble. The delay values stacks actually use are implementation choices.

saying these in an interview costs you the question

  • TCP numbers packets, so ACK 5 means the fifth segment arrived.
  • An ACK confirms only the single segment it is replying to.
  • The receiver passes each segment to the application as soon as it arrives.
  • IP delivers packets in order, so TCP sequence numbers only detect loss.
  • A sender can free a byte's buffer as soon as it has been transmitted once.