Reordering & Discard in 5G NR
In-order delivery, the reordering window, t-Reordering and the discard timer.
The layers below PDCP make no promise about order. HARQ retransmissions stall some transport blocks while later ones sail through; split bearers feed one PDCP entity from two RLC legs with different delays. So PDCP packet 5 can turn up before packet 3. Upper layers — TCP above all — read that reordering as loss and punish it. PDCP fixes it quietly: it numbers every PDU by COUNT, delivers a contiguous run in order, holds a gap open for a bounded time under t-Reordering, discards duplicates, and drops stale transmit data with discardTimer. This page walks the receive- and transmit-side housekeeping defined in TS 38.323, and what all of it must do again at handover.
Introduction
PDCP reordering is the receive-side function that turns the out-of-order stream handed up by RLC/MAC back into the in-order, duplicate-free flow the upper layers expect. It sits at the very top of the user-plane radio stack (TS 38.323), which is the only place a single entity can see across everything that reordered the data underneath it.
In the connection lifecycle it runs continuously on every AM/UM DRB while data flows, but it earns its keep at three moments: during HARQ retransmission bursts, on split/duplicated bearers in dual connectivity, and at handover — where the whole receive and transmit state has to be re-established or recovered without losing or duplicating in-flight packets. Its transmit-side counterpart, the discardTimer, runs in parallel to drop data that has gone stale before it ever reaches the air.
Because the machinery is small — three state variables, one receive timer, one transmit timer, and one window rule — most reordering faults localise cleanly: a gap held too long or too short, a duplicate not discarded, an SDU dropped as stale, or a handover procedure (re-establishment vs data recovery) applied wrongly. Walking those pieces in order is how you turn "throughput stalls" or "packets lost at handover" into a specific cause.
On this page
Why reordering is needed
In plain words: imagine a numbered book printed page-by-page and mailed in separate envelopes, some by express post and some by slow post. Pages arrive jumbled. A careful reader lays them out by page number, waits a little while for a missing page before giving up on it, throws away any duplicate copies, and only ever hands the next reader an unbroken run of pages from where they left off. That reader is PDCP; the page number is COUNT; the "wait a little while" is t-Reordering.
Concretely, TCP and other upper layers interpret out-of-order delivery as packet loss: three out-of-order segments trigger a spurious fast-retransmit and collapse the congestion window, so throughput craters even though nothing was actually lost. Reordering exists to hide the radio's natural disorder from those layers — delivering a clean, monotonic, de-duplicated sequence — while a bounded timer guarantees a single genuinely lost PDU can never block the flow forever.
In-Order Delivery and Duplicate Elimination
PDCP sits at the top of the user-plane radio stack, and it is the single convergence point where two guarantees are made to the layers above: packets are delivered in order, and each packet is delivered once. Everything else on this page is machinery in service of those two sentences.
Receive-side reordering and duplicate elimination keyed on the PDCP COUNT, plus a transmit-side discardTimer that removes SDUs that have waited too long to be sent.
Out-of-order arrival looks like loss to TCP, triggering spurious fast-retransmit and window collapse. Duplicates waste throughput and confuse upper layers. Stale transmit data wastes the air interface on packets the application can no longer use.
Buffer received PDUs indexed by COUNT; deliver the contiguous run below the first missing COUNT; start t-Reordering when a gap appears; on expiry deliver what you have and step past the gap; discard any COUNT already delivered or already buffered.
Two ideas make the whole scheme work and are worth fixing before the detail. First, order is defined by COUNT, not by arrival time — the receiver never trusts when a PDU showed up, only the number it carries. Second, the receiver only ever delivers a prefix: it hands upper layers the longest unbroken run of COUNTs starting from where it left off, and holds everything beyond the first hole. Reordering is therefore not sorting a whole buffer on every arrival; it is answering one question — "has the gap in front of the buffered PDUs been filled?" — and either draining the prefix or waiting.
The receiver's job in one line: keep a buffer indexed by COUNT, always deliver the contiguous prefix, and use one timer to decide how long to wait for a missing PDU before giving up on it.
Where the Disorder Comes From
Reordering only earns its place because three real mechanisms below PDCP deliver PDUs out of order. Understanding them tells you why the function lives at PDCP and nowhere else.
HARQ reordering. When a MAC transport block fails its CRC, HARQ retransmits and soft-combines it, which costs extra slots. A later block that decoded on the first try can be passed up ahead of an earlier block still being retried. RLC acknowledged mode (AM) hides much of this per leg, but not the timing skew that arrives when two legs are involved.
Split bearers. In dual connectivity a single PDCP entity is fed by two RLC legs — for example a leg on the master node (MgNB) and a leg on the secondary node (SgNB) — each with its own latency. A PDU sent later on the fast leg can overtake one sent earlier on the slow leg. Only PDCP sits above both legs, so only PDCP can merge the two streams back into one ordered flow. This is the deciding reason reordering is a PDCP function: RLC operates per logical channel, per leg, and cannot see across the split.
PDCP duplication. For ultra-reliable traffic the transmitter can deliberately send the same PDU down two legs (or two carriers) so that losing one copy still delivers the packet. That reliability trick guarantees the receiver will often get two copies of the same COUNT — which is precisely what the duplicate-elimination logic is there to clean up.
Why PDCP and not RLC: with a split bearer two RLC entities feed one PDCP entity. Any reordering that spans both legs can only happen at the single point above both of them. That point is PDCP.
COUNT, the State Variables and the Reordering Window
Every PDCP PDU is numbered by a COUNT, built from the hyper frame number and the PDCP sequence number so that the value keeps increasing even as the short SN field wraps:
The receive entity keeps three state variables against this COUNT. They are the entire memory of the reordering process, and every rule below is phrased in terms of them.
| Variable | Meaning | Updated when… |
|---|---|---|
RX_NEXT | The COUNT of the next PDCP PDU expected to be received — one past the highest COUNT seen so far. | A PDU arrives with COUNT ≥ RX_NEXT; set RX_NEXT = COUNT + 1. |
RX_DELIV | The COUNT of the first PDU not yet delivered in order to upper layers — the lower edge of the reordering window and the value the whole scheme is trying to advance. | The contiguous prefix is delivered; RX_DELIV moves up to the first still-missing COUNT. |
RX_REORD | The COUNT that triggered the running t-Reordering — the receiver waits until it has delivered everything below this value before stopping the timer. | t-Reordering is started; set RX_REORD = RX_NEXT. |
The reordering window is the span of COUNTs the receiver is willing to hold and reorder at any moment. Its lower edge is RX_DELIV; its size is bounded by the SN length (specifically half the SN space, 2^(SN length − 1)). That bound is what lets the receiver decide, for any incoming COUNT, whether it is a fresh PDU ahead of the window or an old wrapped-around one that must be discarded. A PDU whose COUNT is below RX_DELIV, or that has already been received, sits outside the useful window and is dropped as a duplicate; a PDU inside the window is buffered until the gap ahead of it fills.
RX_DELIV marks the lower edge (first undelivered), RX_NEXT the next expected, and RX_REORD the value captured when the timer started. If 12 arrives, deliver 12,13,14,15 and advance; if t-Reordering expires first, deliver 13,14,15 and jump RX_DELIV to 16.t-Reordering — Bounding the Wait for a Gap
The reordering window can hold a gap open indefinitely if you let it — and a single lost PDU would then block every later PDU forever. t-Reordering is the timer that prevents that. It bounds how long PDCP will stall in-order delivery waiting for one missing COUNT.
The life cycle is precise, and it is defined entirely in terms of the state variables:
- Start: when a received PDU leaves a gap — that is, when
RX_DELIVis belowRX_NEXTand the timer is not already running — PDCP startst-Reorderingand recordsRX_REORD=RX_NEXT. This captures the target the receiver is waiting to reach. - Stop: if the missing PDU(s) arrive and
RX_DELIVcatches up to (or past)RX_REORDbefore expiry, the gap is filled. PDCP delivers the newly contiguous prefix in order, stops the timer, and — if a further gap remains below the newRX_NEXT— restarts the timer for that next hole. - Expiry: if the timer fires first, PDCP gives up on the still-missing PDUs. It delivers all stored PDUs with
COUNTup to (and continuing above) the first still-missing one, advancesRX_DELIVpast the abandoned gap, and if PDUs beyond a later gap remain buffered, startst-Reorderingagain with a freshRX_REORD.
So the timer is a single dial trading latency against ordering fidelity. Set t-Reordering too short and you declare packets lost (or deliver out of order) before a legitimately delayed one arrives — common on a slow split-bearer leg. Set it too long and every packet stuck behind a gap inherits that full delay. It is configured per DRB in pdcp-Config, with values ranging from a few milliseconds up to a few seconds.
| Timer | Side | Started when | On expiry | Configured in |
|---|---|---|---|---|
t-Reordering | Receive | A gap opens (RX_DELIV < RX_NEXT) and no timer running; captures RX_REORD. | Deliver stored PDUs, advance RX_DELIV past the gap, restart if another gap remains. | pdcp-Config (per DRB) |
discardTimer | Transmit | A PDCP SDU is received from upper layers; one instance per SDU. | Discard the SDU (and its PDU if built); if already submitted to RLC, signal discard to RLC. | pdcp-Config (per DRB) |
t-Reordering (restart) | Receive | After delivering a prefix, a further gap still exists below RX_NEXT. | Same as above for the new gap. | — |
✅ Debugging steps (t-Reordering)
- Confirm the configured
t-Reorderingvalue inpdcp-Configis longer than the worst-case skew between the two RLC legs of a split bearer. - When throughput stalls, check whether
t-Reorderingis running — a permanently held gap means the missingCOUNTnever arrived and the timer is too long (or not firing). - On out-of-order delivery to TCP, check whether the timer is expiring before a merely-delayed PDU arrives (timer too short).
- Trace
RX_DELIV/RX_NEXT/RX_REORDacross the event and confirm the start/stop/expiry rules match the observed deliveries.
⚠ Common causes of failure (t-Reordering)
t-Reorderingtoo short — delayed-but-not-lost PDUs on the slow leg are abandoned, so PDCP delivers out of order and TCP sees "loss".t-Reorderingtoo long — a genuinely lost PDU stalls every later packet for the full timer, spiking latency.- A truly missing
COUNT(RLC gave up) combined with a very long timer, so the flow appears frozen until expiry. - State variables mis-tracked (SN-size mismatch) so the window/gap logic misfires.
Two timers, two ends: t-Reordering is a receive-side timer bounding how long you wait for a missing PDU before delivering the rest. discardTimer is a transmit-side timer bounding how long an SDU may wait to be sent before it is dropped as stale. Never conflate them in an interview.
Duplicate Discard, discardTimer and outOfOrderDelivery
Duplicate elimination by COUNT. Because order is defined by COUNT, so is identity. When a PDU arrives, PDCP checks its COUNT: if that COUNT is below RX_DELIV (already delivered) or is already sitting in the reorder buffer, the PDU is a duplicate and is discarded before any further processing. Duplicates come from RLC retransmission timing and, far more often, from deliberate PDCP duplication on split/duplicated bearers, where the receiver is expected to keep the first copy of each COUNT and drop the rest. Duplicate detection is therefore not a special mode — it is the same window test that drives reordering, read from the other edge.
discardTimer — transmit-side staleness. The mirror problem lives at the transmitter. When a PDCP SDU arrives from upper layers, PDCP starts a discardTimer instance for it. If that SDU is still waiting in the transmit buffer when the timer expires — it never got a grant, or lower layers were congested — PDCP discards the SDU (and the corresponding PDU if one was already built). If the PDU had already been handed to RLC, PDCP indicates the discard to RLC so it stops trying to send it. The rationale is that late data is often worthless: for real-time media a packet that misses its playout deadline would be worse than skipped, so spending radio resources to finally deliver it is pure loss. discardTimer is configured per DRB in pdcp-Config (values such as ms10, ms50, ms100… up to infinity).
✅ Debugging steps (discardTimer & duplicates)
- If real-time media stutters, check whether the
discardTimermatches the flow's playout budget — too long keeps stale packets alive; too short drops usable ones. - On a discard, confirm PDCP signalled the discard to RLC when the PDU was already submitted, so RLC stops retransmitting it.
- For duplicated bearers, verify only the first copy of each
COUNTis delivered and later copies are dropped by the window test. - Cross-check unexplained "loss" against
discardTimerexpiries — the SDU may have been intentionally dropped as stale, not lost on air.
⚠ Common causes of failure (discardTimer & duplicates)
discardTimertoo short under congestion — SDUs are dropped before they ever get a grant, looking like packet loss.discardTimer=infinitywith a stalled leg — the transmit buffer grows without bound.- Duplicate not discarded (window/SN mis-tracked) so a repeated
COUNTis delivered twice to upper layers. - Discard not signalled to RLC, so RLC keeps burning air on an SDU PDCP has already abandoned.
outOfOrderDelivery — opting out of ordering. Not every flow wants in-order delivery. Some applications (or the higher layers themselves) prefer to receive each PDU the instant it decodes, accepting reordering rather than the latency of waiting behind a gap. The outOfOrderDelivery option in pdcp-Config tells the receive PDCP entity to deliver PDUs to upper layers immediately as they are received — still performing duplicate discard by COUNT, but not holding PDUs to enforce order. It is the explicit switch that turns the reordering behaviour off for flows that would rather have minimum latency than a clean sequence.
Same window, three jobs: the position of an incoming COUNT relative to RX_DELIV and the window decides everything — below/already-seen → discard as duplicate; inside with a gap ahead → buffer and (maybe) run t-Reordering; contiguous → deliver. outOfOrderDelivery keeps only the first of these.
Reading reordering in the logs
Reordering is visible in the PDCP trace as arrivals annotated with their COUNT, the state variables before/after, and the timer transitions. A gap opening starts t-Reordering; the gap filling stops it; expiry forces delivery past the hole.
Representative UE PDCP receive log — illustrative, values vary by vendor/build:
| Field | Meaning | Example (from log) | Check |
|---|---|---|---|
COUNT | The numbered identity of the arriving PDU. | 13 | Compare against RX_DELIV/window to classify deliver/buffer/duplicate. |
RX_DELIV | Window lower edge / first undelivered COUNT. | 12 → 15 | Should advance only across a contiguous prefix; a stuck value = held gap. |
RX_NEXT | Next expected COUNT (one past highest seen). | 14 | Gap exists whenever RX_DELIV < RX_NEXT. |
RX_REORD | Target captured when t-Reordering started. | 14 | Timer's job is done once RX_DELIV reaches it. |
| t-Reordering start/expiry | Timer lifecycle around the gap. | EXPIRED | Expiry with a real hole = abandoned PDU; too-frequent expiry = timer too short. |
discardTimer expiry | Transmit-side staleness drop. | sn=88, ms50 | Confirm RLC was told; verify the value matches the flow's latency budget. |
Behaviour at Handover: Re-establishment vs Data Recovery
Handover is where all of this is tested, because the UE's radio link tears down and rebuilds while data is in flight. TS 38.323 defines two distinct procedures for what PDCP does then, and mixing them up is a classic interview trap.
PDCP re-establishment. Triggered when the underlying security keys or configuration change — the typical case at a handover with key refresh. The transmit entity resets its state and, for AM DRBs, retransmits from the first PDCP SDU whose successful delivery has not been confirmed by the lower layers, applying the new security keys as it re-processes them. The receive entity, for AM, first delivers to upper layers any stored PDUs that can now be delivered in order, then resets reordering state (RX_NEXT, RX_DELIV, RX_REORD) and stops t-Reordering. Header compression (ROHC) state is reset because the context cannot be assumed to survive the move. For UM and SRBs the handling is lighter — UM does not retransmit, and it resets state so delivery simply resumes at the new cell.
PDCP data recovery. A lighter procedure used for AM DRBs when the connection is preserved but a leg needs to recover — for example on an RLF-triggered recovery or certain SCG changes — without a key change and without discarding buffered data. On data recovery the transmit entity retransmits all the PDCP PDUs previously submitted to the re-established RLC entity, in ascending COUNT order, starting from the first PDU not yet acknowledged — but it keeps the same keys and does not reset COUNT or ROHC. The receive side keeps its window and continues. The point of data recovery is to plug the hole created by the leg change while preserving as much in-flight state as possible.
| Aspect | PDCP re-establishment | PDCP data recovery (AM) |
|---|---|---|
| Typical trigger | Handover with key change / reconfiguration | Leg recovery / SCG change without key change |
| Security keys | New keys applied on re-processing | Keys unchanged |
| ROHC context | Reset | Preserved |
| TX action (AM) | Retransmit from first unacknowledged SDU with new keys | Retransmit PDUs already sent to RLC, from first unacknowledged COUNT |
| RX reordering state | Deliver deliverable PDUs, then reset variables, stop t-Reordering | Preserved; window continues |
The PDCP status report. To avoid re-sending data the target already has — and to prompt the sender to fill genuine holes — the receive PDCP entity can send a PDCP Control PDU carrying a status report. It reports the first missing COUNT (FMC) and a bitmap marking which subsequent COUNTs have and have not been received. The peer transmitter reads it, discards from its buffer any SDU the report confirms as received, and retransmits only the ones flagged missing. Status reporting is enabled per DRB by statusReportRequired (and the peer's pdcp-Config), and it is what makes re-establishment and data recovery efficient rather than a blunt "resend everything."
✅ Debugging steps (handover)
- Identify which procedure applies: a key change → re-establishment (reset ROHC, reset RX state); no key change → data recovery (keep keys/ROHC/
COUNT). - For AM, confirm the TX side retransmits from the first unacknowledged SDU/
COUNT— not from zero, not skipping. - Check whether a
PDCP status report(FMC+ bitmap) was sent/consumed, so the peer skips already-received data. - On re-establishment, confirm ROHC context was reset and reordering variables cleared before delivery resumed.
⚠ Common causes of failure (handover)
- Wrong procedure applied — doing data recovery when keys actually changed (or vice-versa) corrupts
COUNT/ciphering across the move. - ROHC not reset on re-establishment, so decompression fails at the target.
- Missing/ignored status report — the target re-sends data it already has (waste) or fails to fill a real hole (loss).
- Retransmission started from the wrong point, causing duplicates or a gap the receiver cannot close.
Spec note: re-establishment, data recovery, the status report format and the state-variable rules are all defined in TS 38.323. Cite the TS if you are unsure of the exact clause — the procedure names above are the ones the standard uses verbatim.
LTE ↔ NR: LTE PDCP (TS 36.323) already did reordering, duplicate discard, the discardTimer and a status report, but NR restructures the receive logic around the explicit RX_NEXT/RX_DELIV/RX_REORD state variables and a single t-Reordering driven off COUNT, rather than LTE's SN-based reordering that was tied more tightly to RLC. NR also adds the outOfOrderDelivery option and makes reordering central to split/duplicated bearers in NR-DC — the multi-leg convergence case that is far more prominent in NR than in LTE. The PDCP SN can be 12 or 18 bits in NR.
Summary
PDCP reordering delivers a clean, in-order, duplicate-free stream to upper layers from the out-of-order flow HARQ, split bearers, and PDCP duplication hand it. It runs on three state variables against COUNT — RX_NEXT (next expected), RX_DELIV (window lower edge / first undelivered), RX_REORD (the target captured when the timer started) — and one rule: always deliver the contiguous prefix, buffer the rest, and use t-Reordering to bound how long you wait for a missing COUNT. Duplicate discard is the same window test read from the other edge; discardTimer is the transmit-side mirror that drops stale SDUs; outOfOrderDelivery turns ordering off for latency-first flows.
To root-cause a reordering fault, walk the pieces: is t-Reordering mis-sized against the leg skew; is a duplicate slipping through or an SDU being dropped by discardTimer; and at handover, is the right procedure in play — re-establishment (key change: reset ROHC and RX state, retransmit AM data with new keys) versus data recovery (no key change: keep keys/ROHC/COUNT, retransmit from the first unacknowledged PDU) — with a PDCP status report making both efficient. Read the trace state-variable by state-variable and the first value that does not advance as expected is your root cause.
Quick Q&A
Q. Why does PDCP need to reorder — isn't that RLC's job?
A. Lower layers deliver out of order: HARQ retransmissions delay some blocks, and with a split bearer two RLC legs feed one PDCP entity at different latencies. RLC works per leg, so only PDCP — the single convergence point above both legs — can merge and reorder using COUNT, then deliver in order so TCP doesn't mistake reordering for loss.
Q. What do RX_NEXT, RX_DELIV and RX_REORD each track?
A. RX_NEXT is the next COUNT expected (one past the highest received). RX_DELIV is the first COUNT not yet delivered in order — the window's lower edge. RX_REORD is the COUNT captured when t-Reordering started (set to RX_NEXT); the timer's job is done once RX_DELIV reaches it.
Q. What happens when t-Reordering expires?
A. PDCP stops waiting for the missing PDU, delivers all stored PDUs it can in order, advances RX_DELIV past the abandoned gap, and restarts the timer (with a new RX_REORD) if a further gap still remains below RX_NEXT. Without the timer, one lost PDU would block all later PDUs forever.
Q. How are duplicates detected, and what does outOfOrderDelivery change?
A. A PDU whose COUNT is already delivered (below RX_DELIV) or already buffered is discarded — the same window test that drives reordering. outOfOrderDelivery keeps duplicate discard but stops the ordering: PDUs are delivered the instant they decode, trading sequence for lower latency.
Q. Contrast PDCP re-establishment with PDCP data recovery.
A. Re-establishment (handover with key change) applies new keys, resets ROHC and reordering state, and retransmits AM data from the first unacknowledged SDU. Data recovery (leg recovery, no key change) keeps keys, ROHC and COUNT, and just retransmits the PDUs already given to the re-established RLC from the first unacknowledged COUNT. A PDCP status report lets the peer skip data already received.
Q. What is discardTimer for, and how is it different from t-Reordering?
A. discardTimer is a transmit-side timer started per SDU; if the SDU is still unsent at expiry, PDCP discards it (signalling RLC if already submitted) rather than sending stale data. t-Reordering is the receive-side timer bounding how long to wait for a missing COUNT. Different ends, different jobs.
Quick LTE 4G / 5G NR Interview Questions
Q. Why does PDCP need its own reordering function when RLC AM already guarantees in-order, lossless delivery?
A. RLC-level reordering is per-RLC-entity, but a UE can have dual connectivity or handover scenarios where PDCP receives data via more than one path/RLC entity (e.g., split bearers), so PDCP needs to reorder/reassemble across those paths โ a job below-layer RLC alone can't do, since each RLC entity only sees its own sequence.
Q. What happens at PDCP during a handover if some PDUs were already sent to lower layers on the source but not yet acknowledged?
A. PDCP status reporting/re-establishment handles this: unacknowledged/undelivered PDCP SDUs are retransmitted or forwarded to the target via the network (for lossless handover in AM bearers), and PDCP re-establishment resets HFN/COUNT appropriately so ciphering stays consistent across the source-to-target transition.
Related PDCP topics
Reordering, duplicate elimination and discard are three of PDCP's receive/transmit housekeeping functions, and handover ties them to the wider mobility flow. See how deliberate duplication feeds the duplicate-detection logic, how PDCP fits the full user-plane stack, and how the Xn handover carries the data-forwarding these procedures depend on: