How does a framework decide whether to save the session back to its store after a request?
answer
- load, save, destroy under the object
- loaded once per request, then cached
- set and remove mark it dirty
- mutating a stored value in place does not
- no dirty flag, no write-back
basics
~20 sThe framework tracks whether the request changed the session. Storing, removing or clearing an attribute marks it dirty, and a dirty session is written back through the store's save operation; an untouched one usually is not written at all.
solid answer
~40 sBehind the session object sits a small store interface the framework calls: load an entry by identifier, save it, destroy it. Load happens at most once per request, on first access, and the result is cached for the rest of that request. Every attribute set, remove or clear flips a dirty flag. After the handler finishes, the framework saves only if that flag is set — some frameworks save unconditionally instead, and some save an unchanged session anyway to refresh its expiry. The classic bug is mutating an object already inside the session in place: no set call happens, the flag stays clean, the save is skipped, and the change is silently lost on the next request.
go deeper
Know that the session object you use in a handler is backed by a store the framework loads from and saves to, and that your writes are persisted around the edges of the request, not instantly.
Explain the load-once-then-cache rule, which operations set the dirty flag, and why an in-place mutation of a stored value can be skipped by the write-back.
Show the diagnosis: a change that disappears with no error, no store failure in the logs, and a clean dirty flag. Then choose between re-storing, immutable values and save-always with reasons.
Own the policy: save-always trades store write volume for safety, save-if-dirty trades safety for cost, and last-write-wins between concurrent requests is a property you design around, not a bug to patch.
## The store interface behind the session object Whatever the session object looks like in a handler, the framework talks to a much smaller interface underneath, with three operations: - **load(identifier)** — fetch the stored attributes for this client, or report that there are none; - **save(identifier, attributes)** — persist the current attributes; - **destroy(identifier)** — remove the entry entirely, which is what invalidating a session does. Some stores add a fourth, a touch or refresh that extends an entry's life without rewriting its contents. The point of the interface is that it is pluggable: process memory, a shared data store and a database table all satisfy the same three calls, and the handler code above does not change when you swap one for another. ## When each call happens during a request 1. The request arrives carrying an identifier. The framework does **not** load immediately; it defers until the handler first touches the session, so a request that never uses the session costs no store read. 2. On first access, **load** runs once. The attributes are held for the duration of the request, so a handler reading the same attribute ten times performs one store read. 3. The handler reads and writes. Writes go to the in-request copy, not straight to the store. 4. After the handler, before the response finishes, the framework decides whether to call **save**. Step 4 is the interesting one, and it is where frameworks visibly differ. ## Dirty tracking: what marks a session as changed | Operation | Marks dirty | Note | |---|---|---| | Reading an attribute | no | reads never need a write-back | | Storing an attribute | yes | the canonical trigger | | Removing an attribute or clearing | yes | a removal must be persisted too | | Mutating an object already stored, in place | **no** | nothing calls back into the session | | Invalidating the session | n/a | routes to destroy rather than save | The third row is the whole trap. If an attribute holds a collection or a record and the handler modifies that value directly, the session never sees a call and the flag stays clean. With save-if-dirty, nothing is written; the next request loads the old contents and the change has vanished with no error anywhere. Reliable fixes, in order of preference: - **re-store the attribute after mutating it**, so the write path runs; - **keep session values immutable**, so changing one is necessarily a new store; - **mark the session dirty explicitly** where the framework exposes that; - **configure save-always**, accepting a store write on every request that touched the session. ## Save-if-dirty against save-always | | Save if dirty | Save always | |---|---|---| | Store writes | only on change | on every request that used the session | | In-place mutation | silently lost | persisted | | Expiry refresh | needs a touch or a forced save | happens naturally | | Cost under read-heavy traffic | low | one write per request | Neither is universally right. Save-always is forgiving and predictable, and is the safer default when the store is cheap and local; save-if-dirty is what keeps a shared, networked store from taking a write on every page view. The important thing in an interview is to know which one you are relying on, because the in-place mutation bug only exists under the first. ## The concurrency wrinkle Two requests from the same client can overlap — a page and the background call it fired. Both load the same attributes, both modify their own copy, and both save. The second save overwrites the first, so one of the two changes disappears. Dirty tracking does not help here; it only decides *whether* to write, not how to merge. The practical defences are to keep the session small and coarse, to avoid using it as a scratchpad for concurrent work, and to accept that last-write-wins is the semantics on offer. ## What interviewers are listening for The phrase "it saves if something changed" earns partial credit. The full answer connects the three store calls, the once-per-request load, the specific set of operations that mark dirty, and the in-place mutation failure — because that failure is the one that reaches production, produces no exception, and is usually mistaken for a store problem.
- A handler adds an item to a collection already stored in the session and the change is gone next request. Why?Mutating the stored value in place never calls back into the session, so the dirty flag stays clean and the framework skips the save. The next request loads the previous contents. Re-store the attribute after modifying it, keep session values immutable, or mark the session dirty explicitly.
- Why load the session lazily rather than on every request that carries an identifier?Because most requests never touch it. Loading on first access means static-ish and read-only paths pay no store round trip, which matters most when the store is remote. The cost is that the load happens mid-handler, so its latency lands inside handler time rather than before it.
- What does save-always buy you, and what does it cost?It removes a whole class of silent data loss, since in-place mutations are persisted anyway, and it refreshes the entry's lifetime naturally. It costs one store write per request that touched the session, which on a remote store turns every page view into a network write.
saying these in an interview costs you the question
- Thinks every attribute write goes straight to the store immediately
- Believes mutating an object inside the session marks it dirty
- Says the session is loaded once per application rather than per request
- Assumes two concurrent requests merge their session changes
- Cannot name any operation beyond save behind the session object