Premier League Predictor: FastAPI & Redis — Chapter 4, Exercise 3 ===================================================================== TASK Explain, in your own words, why this chapter's own race condition could never be fixed by wrapping the naive SISMEMBER-then-SADD sequence in an ordinary MULTI/EXEC transaction with no WATCH at all. What specifically does WATCH add that plain MULTI/EXEC doesn't have? SOLUTION Trying to fix this by simply wrapping both commands in a bare MULTI/EXEC block runs into a real, structural problem before the race-condition question even comes up: it's not actually possible to write that code the way the fix needs it to behave. Between MULTI and EXEC, every command is only ever queued -- none of them actually run, and none of their results are available to the calling code, until EXEC finally executes the whole batch as a group. That means there is no point inside a MULTI block where the application could read SISMEMBER's own real answer and then decide, based on that answer, whether to queue SADD or not. Both commands would have to be queued unconditionally up front, before either one has actually run -- which defeats the entire purpose, since the app needs to already know whether the team is used before it decides whether SADD should happen at all. Even setting that structural problem aside and imagining some alternate world where a mid-transaction branch were possible, plain MULTI/EXEC still wouldn't solve the real problem this chapter found. MULTI/EXEC's own real guarantee is that the commands inside one EXEC call run as one uninterrupted unit -- it says nothing at all about whether the data being acted on was still fresh at the moment the transaction was assembled. Two separate MULTI/EXEC blocks running one after another are each individually atomic, but there's nothing connecting them to each other; the second one has no way of knowing the first one's own SISMEMBER read is now stale. This is precisely the gap WATCH exists to close. WATCH lets a real, external, un-queued read happen first -- SISMEMBER runs and returns its actual answer immediately, outside of MULTI, so the application genuinely can branch on it. WATCH then adds one real, separate guarantee on top: if the watched key changes at any point between that read and the later EXEC call, the whole queued transaction is refused and a WatchError is raised instead of silently running. That's the one piece plain MULTI/EXEC structurally cannot provide on its own -- a way to make a real decision based on a value read before the atomic block even started, while still being protected if that value changes in the meantime. WHY THIS WORKS AS AN ANSWER ---------------------------- It identifies the real, structural reason a conditional branch can't exist inside a bare MULTI block at all (commands only queue, they don't return real results until EXEC), and separately explains WATCH's own real, distinct contribution -- guaranteeing a pre-transaction read stays valid through to EXEC -- rather than treating WATCH and MULTI/EXEC as interchangeable pieces of the same feature.