In a nested comment thread, how does the shape of the data decide a recursive walker's cases?
answer
- start from how the value is built
- one case per way, not per example
- empty against comment-plus-rest
- recurse on the parts it carries
- empty case returns the neutral value
basics
~20 sThe data definition lists the ways a value can be built, and each way becomes one case. A reply list is either empty or a comment plus the rest, so the walker has exactly those two cases.
solid answer
~40 sA nested comment thread has a recursive definition: a reply list is either **empty** or **a comment followed by the rest of the list**, and a comment carries text plus a reply list of its own. Structural recursion gives the walker one case per way the value can be built, and inside each case it recurses on exactly the parts that have the same type - here, the comment's own replies and the rest of the list. Nothing is guessed: the cases are read off the definition, the recursive call sites are forced by which parts are threads, and the only thing left to decide is how to combine the results. That is why the shape of the answer ends up mirroring the shape of the data.
code
pseudocode · 6 linesfunction countComments(replies)
if replies is empty
return 0
first = head of replies
rest = tail of replies
return 1 + countComments(first.replies) + countComments(rest)go deeper
Be able to say the two ways a reply list can be built and write one branch for each. Knowing that the empty case exists and returns the neutral value is most of the answer at this level.
Explain the derivation as a procedure: enumerate the ways the value is built, one case each, recurse on the parts of the same type, choose a combining step. Show why a "has replies" split is not a case.
Show that the skeleton is reusable across counting, collecting and rendering, and that reviewing a walker reduces to checking cases against the definition. Point out where per-comment cost, not control flow, is the real risk.
Frame it as a convention worth setting: when types are defined inductively and walkers are derived from them, whole classes of missing-branch bugs stop being possible, and the cost is insisting that data shapes be written down first.
## What a recursive data definition actually says A **recursive (inductive) data definition** lists the finite number of ways a value of the type can be built, and says that nothing else is a value of that type. A thread of replies, written that way, is either: - **empty** - no comments at all; or - **a comment followed by the rest of the list** - one comment, plus a reply list that is itself a reply list. And a **comment** is built one way: some text plus a reply list of its own. Two ways to build a list, one way to build a comment, and every thread the program will ever be handed was assembled out of exactly those pieces. That enumeration is not a stylistic note in a design document. It is the specification the walker is written against. ## Reading the cases off the definition Structural recursion is the discipline of letting that enumeration drive the function: 1. Write down every way a value of the type can be built. 2. Give the function exactly one case per way. 3. In each case, name the parts that way carries. 4. Recurse on exactly the parts that have the same type, and combine those results with whatever the case contributes itself. For a reply list, that mechanical procedure produces this: | Way the value was built | Parts it carries | Recursive positions | What the case must produce | |---|---|---|---| | empty list | none | none | the answer for nothing at all - the neutral value | | comment + rest of list | the comment, the rest of the list | the comment's own replies, and the rest | the comment's own contribution combined with both recursive results | Notice how little judgement is left. The number of cases is fixed by the definition. The recursive call sites are fixed by which parts are again threads. The only genuinely free decision is the **combining operation** - addition for a count, concatenation for a flattened list, maximum for a depth - plus the value the empty case returns, which has to be the neutral element of that operation. ## Why derived cases beat guessed ones - **No missing case.** A case per constructor cannot silently omit the empty list, which is the shape a guessed walker most often forgets. - **No invented case.** "A comment with replies" versus "a comment without replies" looks like a third case, but it is not a way a comment is built - it is a test on a comment's reply list, which the list's own two cases already cover. Splitting on it duplicates logic that the recursion handles for free. - **The recursion cannot wander.** Each recursive call is handed a part the current value already carries, so the walker descends the thread it was given rather than re-deriving some other value to walk. - **The code documents the data.** A reader who has never seen the type can reconstruct its definition from the walker's cases, because they are the same list. - **Review is mechanical.** "Is there a case for every way this is built, and does each recursive call take a part?" is a question a reviewer can answer without running anything. ## What the shape decides, and what it leaves open The shape decides the *skeleton*. It does not decide the *meaning*. Counting comments, collecting author names, finding the deepest nesting and rendering an indented view are four different functions with identical case structure and different combining steps. That is exactly the payoff: once the skeleton is derived, the remaining work is small and local, and a bug is almost always in the combining step rather than in the control flow. Several things the shape explicitly does not hand you: - **Visit order.** Handling a comment before descending into its replies, or after, is a free choice; both are structural. - **Cost.** The walker touches every comment once, so the work is proportional to the number of comments in the thread - but the shape says nothing about how expensive the per-comment step is. - **Stack depth.** The number of nested frames follows the *nesting depth* of the thread, which is a property of the data, not something the discipline bounds. - **What to do with a malformed value.** If a thread can arrive with something the definition does not allow, that is a validation concern at the boundary, not another case in the walker. The habit worth building is the first step, not the last: before writing any branch, say out loud the ways a value of this type can be built. The cases follow from that sentence, and a walker whose cases do not line up with it is a walker that will surprise someone.
- The empty reply list returns 0 for a count. What decides that value for a different walker?The combining operation does. The empty case must return the value that leaves the combination unchanged: `0` when results are added, an empty collection when they are concatenated, the smallest sensible value when they are maximised. Choosing a value that is not neutral for the operation quietly skews every result that passes through an empty list.
- Is splitting a comment into "has replies" and "has no replies" a third case?No. Those are not two ways a comment is built - a comment always carries a reply list, and that list's own two cases already distinguish them. Writing the split by hand duplicates work the recursion does anyway, and the duplicated branch is where the two copies drift apart later.
- Does the derived skeleton change if the walker must visit replies before the comment itself?No. Visit order is a free choice inside each case: the same cases and the same recursive calls, with the combining step arranged differently. Both orders are structural, because both hand each recursive call a part the current value already carries.
Unpacking a set of nested boxes: you do not plan the moves in advance, you open whatever box you are holding and repeat on whatever is inside it, and you stop because a box is eventually empty.
saying these in an interview costs you the question
- Says the number of cases depends on how deep the thread is
- Forgets the empty case and handles emptiness by accident
- Adds a case for "a comment with no replies" as if it were a constructor
- Thinks structural means one recursive call, so a list of children is impossible
- Picks an empty-case value that is not neutral for the combining step