Coursefy progress
Produce an end-to-end design whose data paths, capacities and failure behavior satisfy explicit requirements.
THE IDEA
Coursefy progress
Published content is versioned and progress is tied to stable lesson ids. XP for a first completion must be idempotent: key the reward on (user, lesson, reward version) and award it once. If 100 learners finish the same lesson and every request is retried once, there are 200 deliveries but only 100 events: 2,000 XP is right, 4,000 is a bug. The dedup record and the XP change must share one atomic durable step, and merging a guest account must replay its events through the same rule so nothing is awarded twice.
Predict first. Is adding XP on every repeated completion request correct when XP is for the first completion?
Chapter context
A case study integrates choices into one coherent system. Start with a small requirement set, trace the important operations, calculate the dominant costs and stress the design with a failure before adding components.
TRY IT YOURSELF
Part 1. Do it hands-on
Compute correct and naive XP by hand, then write an idempotent progress store with guest merging.
- To do: 100 learners each complete the same lesson, and every completion request is delivered twice (a retry). Deliveries?
- To do: Distinct (user, lesson, reward version) events?
- To do: At 20 XP per first completion, the correct total XP?
- To do: A handler that adds XP on every delivery would award?
- To do: Is adding XP on every repeated completion request correct when XP is for the first completion? Type yes or no
Part 2. Do it in code
- class ProgressStore with self.completed (set of (user, lesson, reward_version)) and self.xp (dict user → XP).
- complete(user, lesson, reward_version, reward=20): award `reward` XP only the first time the logical event (user, lesson, reward_version) is seen; return True if XP was awarded, False for a repeat. The event record and the XP change are applied together.
- total_xp(user): the user's XP (0 if none).
- merge_guest(store, guest, account): move the guest's completions and XP into the account without awarding anything twice: every guest event not already completed by the account is added with its XP once; events the account already has add nothing. Remove the guest.
- expected_xp(deliveries, reward): reward × the number of distinct events in the list of (user, lesson, version) deliveries.
Stuck, or something not working? You can come back to it any time.
CHECK YOUR UNDERSTANDING
Is adding XP on every repeated completion request correct?
Chapter reference: math, worked example & code
This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.
Math and notation
For first-completion XP, define a unique logical event key such as (user_id, lesson_id, reward_version). Under a fixed reward r, count(distinct eligible event keys). The deduplication record and awarded effect must share an atomic durable boundary in a real implementation.
Worked example
One hundred learners complete the same lesson, and every completion request is retried once. There are 200 deliveries but only 100 distinct learner-lesson events. At 20 XP per first completion, the correct total is 2000 XP, not 4000. The Python example models the invariant without pretending to implement distributed persistence.
Reference code
The following example uses the Python standard library and does not call a remote service.
deliveries=[]
for user in range(100):
deliveries.extend([(user,'lesson-1','reward-v1')]*2)
unique=set(deliveries)
assert len(deliveries) == 200
assert len(unique) == 100
assert 20*len(unique) == 2000
assert 20*len(deliveries) == 4000
Python examples are provided for study and local execution; this page does not execute Python.
Common mistakes
A keyspace count does not prove collision freedom. Delivery and reading are different product events. Deduplication must cover both durable event identity and the actual effect.