---
title: "TCP Connection Teardown"
description: "Four-way handshake, FIN/ACK, TIME_WAIT"
source: https://vizlearn.app/cs/networks/tcp-teardown
access: premium (interactive player requires Lifetime; this doc is free)
---

# TCP Connection Teardown

TCP connection teardown is shown here as the four-segment exchange — `FIN`, `ACK`, `FIN`, `ACK` — that gracefully closes an established TCP connection. Each direction of the connection closes independently, the active closer walks `FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED`, the passive closer walks `CLOSE_WAIT → LAST_ACK → CLOSED`, and the closer that sent the first `FIN` lingers in `TIME_WAIT` for twice the maximum segment lifetime.

Think of it as ending a phone call between two polite people. You say "I'm done talking." The other person says "got it" — but they're not done, so they keep talking for a while, then say "okay, I'm done too," and you say "got it" back. Four separate utterances, because being done *talking* doesn't make the other person done. And then — this is the part everyone forgets — you stay on the line a few extra seconds, in case your last "got it" never made it through. That extra wait on the line is `TIME_WAIT`, and it does real work.

## How TCP Connection Teardown Works

Opening a connection takes three segments; closing it takes four. The asymmetry comes from what a TCP connection actually is: two independent one-way byte streams, Client→Server and Server→Client, sharing one socket pair. Each stream closes on its own schedule.

One peer is the **active closer** — its application calls `close()` first; here that's the Client. The other is the **passive closer**. The active closer sends a `FIN` ("my stream is finished") and the passive closer acknowledges it immediately. But the passive closer's own stream may still be mid-sentence — buffered data, a response still being generated — so its `ACK` and its own `FIN` are two separate segments, possibly far apart in time. That gap is the **half-close**: one pipe shut, the other still flowing. The handshake fuses `SYN` and `ACK` into one segment because a fresh connection has nothing in flight; the teardown's middle `ACK` and `FIN` cannot be fused in general, which is why four waves instead of three.

Each segment drives one state transition on one of two ladders. The active closer descends `ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED`; the passive closer descends `ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED`. Read the two ladders side by side and you have read the whole protocol.

The lesson uses symbolic sequence numbers — the Client's `FIN` carries `seq = u`, the Server's carries `seq = w` — rather than real 32-bit byte counters, and a `FIN` consumes one sequence number even though it carries no data. That's why the acknowledgments are `u+1` and `w+1`, not `u` and `w`.

## TCP Connection Teardown Step by Step

The default scenario is the **Normal close**, and it runs in thirteen beats:

1. Both peers sit in `ESTABLISHED`. The Client's application calls `close()` — Client becomes the active closer, Server the passive one.
2. The Client sends segment 1: `FIN, seq = u`. Client: `ESTABLISHED → FIN_WAIT_1`.
3. The Server receives the `FIN` and now knows the Client will send no more data. Server: `ESTABLISHED → CLOSE_WAIT`.
4. The Server sends segment 2: `ACK, ack = u+1`, acknowledging the `FIN` right away.
5. The Client receives that `ACK`. Client: `FIN_WAIT_1 → FIN_WAIT_2`. The Client→Server pipe is now closed.
6. Half-close. The Server may still have data to deliver — one person on the call is done talking but still listening. Its `ACK` (already sent) and its `FIN` (not yet sent) stay separate; this beat is why the close takes four segments.
7. The Server finishes sending and emits segment 3: `FIN, seq = w, ack = u+1`. Server: `CLOSE_WAIT → LAST_ACK`.
8. The Client receives the Server's `FIN`. Client: `FIN_WAIT_2 → TIME_WAIT`.
9. The Client sends segment 4, the final `ACK, ack = w+1`.
10. The Server receives it. Server: `LAST_ACK → CLOSED`. The Server is completely done — note that it gets there *before* the Client.
11. The Client lingers in `TIME_WAIT` for `2·MSL`, ready to re-acknowledge a retransmitted `FIN` and letting any stray duplicates of this connection's segments expire in the network.
12. The timer elapses. Client: `TIME_WAIT → CLOSED`.
13. A closing beat holds both ladders at `CLOSED`. Four segments plus one deliberate wait, and the connection no longer exists.

On the canvas, each segment is a chip crossing the channel between the two lifelines, and every state change drops a badge onto its peer's ladder the moment it happens — older rungs fade so the current state is always the loudest. The packet in flight and the `TIME_WAIT` countdown run at slow-motion beats so you can read the `seq`/`ack` arithmetic mid-crossing.

## Why TIME_WAIT Exists

`TIME_WAIT` looks like dead air — both `FIN`s sent, both acknowledged, why is the Client still holding the socket? Because a pure ACK is not itself acknowledged, including this final ACK. The Client cannot know it arrived.

Switch the scenario selector to **Lost final ACK** and the lesson plays the failure that justifies the wait. The Client sends `ACK, ack = w+1` — and the network eats it. The Server, still in `LAST_ACK`, hears nothing; its retransmission timer fires and it re-sends `FIN, seq = w`. The Client is still on the line in `TIME_WAIT`, so it simply re-ACKs with `ack = w+1` and restarts its `2·MSL` timer. The Server receives the ACK this time and moves to `CLOSED`; the Client serves out its linger and closes too. Same ending, one extra round trip.

Now run the counterfactual. If the Client had closed immediately after beat 9, the retransmitted `FIN` would arrive at a socket that no longer exists, and the Client's stack would answer with a `RST` — a slammed receiver instead of a "got it." The Server would end on an error rather than a clean close. The `2·MSL` figure (twice the maximum segment lifetime) is sized so a retransmitted `FIN` and its re-ACK can each cross the network once before the wait ends, and so every old segment from this connection has died before the same socket pair can be reused.

## Edge Cases

- **Lost final ACK** — the selectable second scenario, traced above: Server retransmits its `FIN` from `LAST_ACK`, Client re-ACKs from `TIME_WAIT` and restarts the timer.
- **Simultaneous close.** Both applications call `close()` at once; each side sends a `FIN` before seeing the other's and goes `FIN_WAIT_1 → CLOSING → TIME_WAIT`. Not animated here — there is no passive closer to contrast against.
- **`FIN_WAIT_2` starvation.** If the passive side never sends its `FIN`, the active closer would wait forever in `FIN_WAIT_2`; real stacks may apply cleanup timeouts, especially after the application has abandoned the connection; the exact policy depends on the implementation and socket state.
- **Abortive close.** A `RST` skips the whole four-way exchange — no half-close, no `TIME_WAIT`, and any in-flight data is discarded.

## Common Mistakes

- **Expecting three segments, like the handshake.** The middle `ACK` and the second `FIN` cannot merge in general — the passive closer may still be sending. The half-close window is the load-bearing difference between opening and closing.
- **Believing both sides reach `CLOSED` together.** The passive closer is done the instant the final `ACK` lands (beat 10); the active closer holds `TIME_WAIT` for `2·MSL` first. In this normal-close scenario, the Server finishes before the Client.
- **Treating `TIME_WAIT` as waste.** It is the only thing standing between a lost final `ACK` and a `RST`-terminated close — and it's why servers that actively close thousands of connections accumulate sockets in `TIME_WAIT`.
- **Putting `TIME_WAIT` on the wrong peer.** In this normal-close scenario it belongs to the active closer, not the passive peer. Simultaneous close can put both endpoints in TIME_WAIT.
- **Mixing up the acknowledgment arithmetic.** A `FIN` consumes one sequence number despite carrying no data, so the ACKs are `u+1` and `w+1`, not `u` and `w`.

## A Note on Simplification

The lesson idealizes the rest: symbolic `u`/`w` instead of real byte counters, no data segments interleaved, timers as single beats. For the full state machine, RFC 9293 is the current word.
