Why does DFS postorder, not preorder, give the correct order for releasing nested resources?
answer
- two timestamps, one on entry one on exit
- intervals nest or stay disjoint, never overlap
- children finish before the parent can
- reverse of postorder flips the siblings
- innermost released first, outermost last
basics
~20 sIn depth-first search a vertex finishes only after everything below it has finished, so postorder emits the innermost items first — exactly what release requires. Preorder emits a container before its contents, which is acquisition order, not teardown order.
solid answer
~50 sDepth-first search assigns each vertex two timestamps: a discovery time when it is entered and a finish time when its neighbour loop ends. Preorder lists vertices by increasing discovery time, postorder by increasing finish time. The structural fact is that for any descendant `v` of `u`, `d[u] < d[v] < f[v] < f[u]` — the intervals nest like balanced parentheses and never partially overlap. Ordering by finish time therefore guarantees every descendant appears before its ancestor, which is precisely the release rule: nothing is torn down while something contained in it is still alive. Preorder guarantees the mirror image, ancestors first, which is what you want for setup. And postorder is not simply preorder reversed: for a root `r` with children `a` then `b`, preorder is `r, a, b` while reversed postorder is `r, b, a`.
go deeper
Know that a vertex is recorded twice — once on the way in, once on the way out — and that the way-out order lists children before parents. Recall which of the two you would use to tear something down.
Explain the nesting property with the two timestamps, show the two-child counterexample proving postorder is not reversed preorder, and map preorder to acquisition and postorder to release.
Demonstrate the constant-time containment test from the interval pair, and know why the failure mode of using preorder for teardown is silent on shallow or single-child fixtures and only bites in deep, wide structures.
Own the lifecycle contract: which ordering guarantees the system promises, what happens when a resource is held from several places so the nesting is traversal-dependent, and where a partial-failure teardown leaves the invariants.
## Two clocks, not one A depth-first search naturally stamps every vertex twice. Keep one counter that ticks on every event; when the traversal enters vertex `u` record `d[u]` (discovery), and when `u`'s neighbour loop finishes record `f[u]` (finish). Over `V` vertices the counter runs to `2V`, and each vertex owns the half-open interval `[d[u], f[u]]`. - **Preorder** = the vertices listed by increasing discovery time — recorded on the way in. - **Postorder** = the vertices listed by increasing finish time — recorded on the way out. Both orders contain exactly the same vertices. That is the trap: because the *sets* match, people assume one sequence is the reverse of the other. ## Postorder is not reversed preorder Take a root `r` with two children `a` and `b`, explored in that order. - Preorder: `r, a, b`. - Postorder: `a, b, r`. - Reversed postorder: `r, b, a`. `r, a, b` is not `r, b, a`. Reversing postorder does restore the property "ancestors before descendants", but it flips the order among sibling subtrees, so it is a different sequence from preorder as soon as any vertex has two children. Only on a path — one child everywhere — do the two coincide. ## The nesting property The structural result that makes postorder useful: for any two vertices `u` and `v` explored in the same depth-first search, their intervals `[d[u], f[u]]` and `[d[v], f[v]]` are either **completely disjoint** or **completely nested**. They can never partially overlap. `d[u] < d[v] < f[u] < f[v]` is impossible. The reason is mechanical. Suppose `v` is discovered while `u` is in progress. Then `v` was entered from inside `u`'s neighbour loop, directly or through a chain of entries, and every one of those entries must return before `u`'s loop can continue. So `f[v] < f[u]`, and the interval of `v` sits wholly inside the interval of `u`. Write each discovery as an opening parenthesis and each finish as a closing one, and the whole traversal reads as a balanced parenthesis string — which is why this is usually called the parenthesis structure. Two consequences fall straight out: - `v` is a descendant of `u` in the search exactly when `d[u] < d[v]` and `f[v] < f[u]`. Two integer comparisons answer a containment query in constant time once the traversal has run. - Ordering by increasing finish time puts every descendant strictly before its ancestor. ## Why release wants the second consequence Model a nested resource arrangement as a graph: an edge from a holder to a thing it holds. A buffer holds views into it; a session holds open handles; an outer scope holds inner ones. The rule for teardown is that you may release something only once nothing that lives inside it is still alive. That is exactly "all descendants first, then the vertex", which is exactly increasing finish time. So you run the search, record finishes, and release in that order — the innermost items come out first and the outermost last, without any extra sorting pass. Preorder gives you the mirror rule: a vertex before everything under it. That is the correct order for **acquisition** — you must open the session before you can open a handle inside it — which is why the same traversal, with the emit moved from the entry to the exit, serves both phases of a lifecycle. The practical failure mode when someone reaches for preorder here is silent: on a shallow structure, or one where each holder has a single child, preorder and reversed postorder look the same and the code appears to work. The teardown only misfires when a holder has several children, or when the nesting is deeper than the test fixture. ## Cost, and one caution Recording both timestamps changes nothing asymptotically: the traversal is O(V+E) and the two integer arrays are Θ(V). Emitting on the way out costs nothing extra either — postorder is one line moved. The caution: this reasoning is about the search's own nesting relation, the ancestor-descendant structure the traversal builds. Discovery and finish times describe *that* structure faithfully; when a vertex is reachable from several unrelated places, which of them becomes its ancestor depends on the traversal order, so "finished before" is a statement about the search tree, not an intrinsic property of the graph.
- Given only the timestamp arrays, how do you test whether one vertex is a descendant of another?Compare intervals: `v` is a descendant of `u` exactly when `d[u] < d[v]` and `f[v] < f[u]`. Because intervals are either nested or disjoint and never partially overlap, those two comparisons are conclusive — a constant-time containment test after a single O(V+E) traversal, with no repeated walking of the structure.
- Which timestamp pattern is impossible for two vertices in one depth-first search, and why?`d[u] < d[v] < f[u] < f[v]` — partially overlapping intervals. If `v` is discovered while `u` is in progress, `v` was entered from within `u`'s neighbour loop, and every entry made there must return before that loop can continue. So `f[v]` necessarily precedes `f[u]`, and the interval nests rather than straddles.
- If postorder handles release, what handles the setup half of the same lifecycle?Preorder: emit each vertex at discovery, before descending. That guarantees a holder appears before everything it holds, which is the acquisition rule — you cannot open something inside a container that is not open yet. It is the same traversal with the emit line moved from after the neighbour loop to before it.
Discovery and finish times are opening and closing parentheses: a nested pair closes before the pair around it, so reading closes in order gives you innermost-first.
saying these in an interview costs you the question
- Says postorder is simply preorder reversed
- Believes discovery and finish intervals can partially overlap
- Uses preorder for teardown of nested resources
- Thinks a second timestamp costs an extra traversal
- Cannot state why a child always finishes before its parent