In a Server-Sent Events stream, what is the client's last event ID after an event that carries no `id:` field?
answer
- one buffer resets, one does not
- checkpointing, not labelling everything
- an empty value is not a no-op
- no field sent when the remembered id is empty
- carried forward across unlabelled events
basics
~20 sThe client's last event ID is unchanged: it is set only when an event carries an id field, and it persists through every later event that omits one, so a feed can label every hundredth event and still resume from that checkpoint.
solid answer
~40 sThe last event ID is **sticky**. It is assigned when an `id:` field appears and it is not cleared when an event is dispatched, so events that carry no `id:` leave it exactly as it was. That is deliberate: it lets a feed label a checkpoint every so often instead of every event, and a reconnect still returns the most recent label. The one way to clear it is an `id:` whose value is empty, which sets the remembered id back to the empty string — and a client with an empty last event ID sends no `Last-Event-ID` field at all, so the server sees a fresh subscriber. Use that on purpose when nothing before this point is resumable; reach it by accident and you have silently turned off resumption.
code
http · 10 linesid: 20481
event: level
data: {"gauge":"upper-weir","metres":3.41}
event: level
data: {"gauge":"upper-weir","metres":3.44}
id:
event: reset
data: {"reason":"gauge catalogue renumbered"}go deeper
The point to carry away is that an id sticks: once an event has set one, later events without an id do not erase it, and a reconnect still returns that value.
Explain the two buffers and why they differ, then describe checkpointing: labelling every Nth event trades duplicate replay against per-event id bookkeeping.
Show that you audit your emitter for a path that writes an empty id value, because that silently disables resumption across every client with nothing logged.
The call worth owning is labelling granularity as a contract: how much duplicate delivery consumers must tolerate, decided once for a feed rather than per endpoint.
## Sticky, not per-event Two buffers behave differently as a Server-Sent Events response is parsed, and the difference is the whole of this question. The accumulated `data` and the event name are consumed and reset every time a blank line dispatches an event. The remembered id is not. It is written when an `id:` field appears and it simply stays there. So in a stream of gauge readings: - an event with `id: 20481` sets the remembered id to `20481`; - the next two events, with no `id:` line at all, dispatch normally and leave it at `20481`; - a reconnect at that moment sends `Last-Event-ID: 20481`. The client is not reporting the last event it saw. It is reporting the last **label** it was given. ## Why the design is useful Labelling every event is not free. The id has to mean something against the store you replay from, and on a high-rate feed that can mean a write or a lookup per event. Stickiness buys you a choice: | labelling strategy | what a reconnect resumes from | cost | |---|---|---| | every event labelled | the exact event last received | an addressable id per event | | every Nth event labelled | the last checkpoint, so up to N-1 events replay again | one id per checkpoint | | nothing labelled | nothing — no field is sent, the client looks new | none, and no resumption | The middle row is the one stickiness enables. A catchment feed can label a checkpoint each minute, or each time a reading is committed to durable storage, and let the smaller updates between checkpoints ride unlabelled. The price is duplicates on resume: the client may be sent readings it already displayed, so the receiving application has to tolerate seeing a value twice. ## Clearing it on purpose An `id:` field with an empty value is not a no-op. It assigns the empty string, and the rule on the client side is that a `Last-Event-ID` field is sent only when the remembered id is non-empty. Sending an empty `id:` therefore says: *from here, there is nothing to resume from*. The next reconnect arrives looking exactly like a brand-new subscriber, and your handler will do whatever it does for one of those — typically send a current snapshot. That is genuinely useful after a discontinuity the old ids no longer address: a catalogue of gauges was renumbered, a replay store was rotated, a feed switched to a new sequence. It is also a foot-gun, because an emitter that writes an id field from an empty value by accident turns resumption off for every client reading that stream, and nothing anywhere reports it. ## What an id can and cannot contain The remembered value has to survive a round trip into a request header field, and two constraints fall out of that: 1. **No line breaks, ever.** A line ending is what terminated the `id:` field on the way out, so an id containing one was never expressible. 2. **A NULL character disqualifies the value.** An `id:` field whose value contains one is ignored rather than stored, so the remembered id keeps its previous value — which, note, is not the same as being cleared. Beyond that the value is an opaque string, encoded as UTF-8 when it is sent back. Numbers are conventional but nothing requires them. Keep it short: it is carried on every reconnect by every client, and a long id multiplies across a fleet that all come back at once. ## The check to run on your own emitter Ask two questions of the code that writes events. First, *when do I emit an id, and is the gap between ids an amount of replay I am willing to pay for?* Second, *can any code path emit an empty id value?* The first sets your resumption granularity. The second is the one that removes it without telling anyone.
- Why would an emitter deliberately send an `id:` with an empty value?To declare that nothing before this point is resumable — after the ids were renumbered, or a replay store was rotated. The client's remembered id becomes empty, it sends no `Last-Event-ID` on the next reconnect, and the handler treats it as a new subscriber and sends a snapshot instead of a replay.
- If only every hundredth event is labelled, what does a resume cost?Up to ninety-nine events are replayed that the client already had, because the server can only resume from the last label it gave out. That is a real trade: sparse labelling saves per-event id bookkeeping and pays for it in duplicate delivery, which the receiving application must be able to absorb.
saying these in an interview costs you the question
- Thinks the remembered id is cleared each time an event is dispatched.
- Believes every event must carry an id for resumption to work at all.
- Reads an empty id value as a field that is simply ignored.
- Assumes the client counts events itself when no id is present.
- Expects sparse ids to resume exactly, with no duplicate delivery.