Your appointment-date producer keeps part of its repeat rule outside the seed — what does that cost you?
answer
- what the seed must name
- step is a function of its argument
- resume, replay, share
- hidden counter breaks repeatability
- same seed, same remaining series
basics
~20 sThe step stops being a function of the seed alone, so the seed no longer names a position in the series: you cannot resume or replay from it, two readers can disagree, and caching results by seed becomes unsound.
solid answer
~50 sA seed-and-step producer works because the step has one input and two outputs: `step(seed)` returns either a stop marker or a pair of **one element and the seed for the rest**. That shape is what lets a generic driver run the producer without knowing anything about the repeat rule. Move part of the rule's state outside — an occurrence counter, a cursor, a mutable field the step reads and updates — and the seed stops describing the remaining production. Concretely you lose three things: you cannot store a seed and resume the series later, because the hidden part is gone; two readers holding the same seed can see different dates, because whoever ran first advanced the hidden state; and any cache keyed on the seed is unsound, because the same key now maps to different answers. Put the whole position in the seed and all three come back.
code
pseudocode · 12 lines// hidden state: the step reads a counter it does not carry
occurrencesTaken = 0
function stepHidden(seed)
date = seed.anchor plus seed.interval * occurrencesTaken
occurrencesTaken = occurrencesTaken + 1
return pair(date, seed) // same seed handed back every time
// complete seed: the position travels inside the value
function step(seed)
if seed.left equals 0 then return STOP
date = seed.anchor plus seed.interval * seed.taken
return pair(date, seed with taken = seed.taken + 1, left = seed.left - 1)go deeper
Remember what a seed is for: it describes what is still to be produced, not what has been produced already. Keeping a counter outside it is the common first mistake in a hand-written generator.
Explain the step's shape — one seed in, a stop marker or an element plus the next seed out — and why a step that reads state held elsewhere no longer honours it, even though the code still compiles and runs.
Show the failures you have actually seen: a series that restarted from the beginning after a redeploy, or two readers of one seed disagreeing. Then say what you would put inside the seed to close each one.
Frame it as the value-versus-process choice. A complete seed makes a position in an unbounded series something you can store, hand across a boundary and compare; deciding that producers in your system must be expressible that way is a standard, not a local fix.
## What the seed is supposed to be In a seed-and-step producer, the seed is not scratch space and it is not a loop variable. It is a **complete description of everything still to be produced**. Hand the same seed to a fresh copy of the step, in a fresh process, a week later, and the remaining dates should be exactly the dates you would have got by continuing. That is the whole contract, and it is what makes the step's signature usable by code that knows nothing about appointments: - Input: one seed. - Output: a stop marker, or a pair of one element and the next seed. A driver written against that signature can run any rule at all. The moment the step needs something the seed does not carry, the driver's guarantees quietly stop holding while its type still type-checks. ## The three things hidden state takes away **Resumability.** Storing a seed is how a producer survives a restart: keep the seed, come back, keep producing. If the occurrence counter lives in a variable beside the step rather than inside the seed, the stored seed is a partial snapshot, and resuming produces the series from the wrong place — usually from the beginning, which is worse than an error because it looks plausible. **Repeatability across readers.** Two parts of a system may legitimately hold the same seed: one rendering the next five occurrences on screen, another checking for conflicts. With a complete seed both see the same five dates, in any order, any number of times. With a hidden counter, whichever runs first advances it, and the second gets a different answer for the same seed. That is a bug that only appears when a second reader is added, often long after the producer was written. **Cacheable identity.** A seed that fully describes the rest of the series can be used as a key: same seed, same remaining dates, so results can be stored, compared, or deduplicated. Hidden state breaks the key without breaking the lookup, so the cache returns answers computed under a state that no longer exists. ## Complete against partial seeds | | complete seed | seed with state left outside | |---|---|---| | step depends on | its argument only | argument plus something else | | store and resume | works | resumes from the wrong position | | two readers, one seed | identical series | depends on who ran first | | key a cache by it | sound | same key, different answers | | test in isolation | construct a seed, call the step | reproduce the surrounding state first | | driver can be generic | yes | only if it also drives the hidden part | ## How to tell whether a seed is complete 1. Write the seed down as data — no references to anything mutable. If some field cannot be written down, it is not yet in the seed. 2. Ask what the next element is, using only what you wrote down. If answering needs a counter someone else is holding, a cursor, or a read of the current time, the seed is partial. 3. Ask whether the step could be called twice on that seed with the same result. Two calls that disagree mean the hidden state moved. 4. Ask what the seed must carry for **this particular rule**. A simple fixed interval may be resumable from the last date alone; a rule like *the second Tuesday of every month* or *ten occurrences from the anchor* needs the position or the remaining count as well, because the last date does not determine them. That last point is where careful candidates go wrong in the safe direction: they assume the last emitted element is always enough to continue. For some rules it is. For rules whose next element depends on how many have already been produced, or on an anchor the elements do not reveal, it is not. ## The judgment underneath The deeper reason to insist on a complete seed is that it converts a process into a value. A process must be driven exactly once, in order, by whoever owns it. A value can be stored, passed to another component, compared with another value, or thrown away and reconstructed. The whole reason to express a recurring series as a seed plus a step, rather than as a loop that pushes dates into a list, is that the seed is a value naming a position in an unbounded series. Leaving any part of that position outside the seed keeps the code looking corecursive while giving back the properties the style was chosen for.
- What exactly should the step return?Either a stop marker or a pair: one element, and the seed for the rest of the production. Returning only the element forces every caller to work out the position for itself; returning only the next seed loses the element. The pair-or-stop shape is what lets one generic driver run a producer whose rule it knows nothing about.
- Is the last emitted date always enough to resume the series?No. For a plain fixed interval it usually is. For a rule whose next date depends on how many occurrences have already been produced, or on an anchor the dates themselves do not reveal, the last date underdetermines the rest. The safe test is whether a fresh step given that value alone would produce exactly the remaining dates.
- How does a complete seed help testing?It makes the producer testable one step at a time. Construct a seed as plain data, call the step, and assert on the pair it returns, with no surrounding state to arrange or reset. A step that reads state held elsewhere forces every test to reproduce that state first, and to undo it afterwards.
saying these in an interview costs you the question
- Treats the seed as a loop variable owned by the caller
- Believes hidden step state is harmless if the output looks right
- Thinks the last emitted date is always enough to resume
- Confuses the seed with the element just handed back
- Says the step may return only the next seed