skip to content

A colleague proposes one regular pattern to validate arbitrarily nested bracket expressions. What argument refutes it on the spot?

level: middleimportance: must knowfreq 70%

answer

  1. finite memory versus unbounded depth
  2. pigeonhole on repeated states
  3. witness: p opens then p closes
  4. the |xy| <= p clause pins y
  5. pump to i = 2, counts diverge

basics

~20 s

A finite-state recogniser has finitely many states, so it cannot track unbounded nesting depth. The pumping lemma makes that concrete: repeating a block of opening brackets inside a long balanced witness yields an unbalanced string the pattern must still accept.

solid answer

~50 s

State it as the pumping lemma, used contrapositively. If a regular pattern accepted exactly the balanced strings, there would be a pumping length `p` such that every accepted string of length at least `p` splits as `x y z` with `y` non-empty, `|xy| <= p`, and `x y^i z` accepted for every `i >= 0`. Choose the witness of `p` opening brackets followed by `p` closing brackets. Since `|xy| <= p`, the block `y` lies entirely inside the opening run, so taking `i = 2` produces a string with more opens than closes — unbalanced, yet the lemma says the pattern accepts it. That contradiction kills every pattern at once, not just this one. In review, the constructive outcomes are to cap the nesting depth, which is finite state, or to validate with something that keeps a counter.

go deeper

for a junior

Recall the headline: a finite-state recogniser has no memory beyond its states, so it cannot count nesting depth without bound. Balanced brackets need that count.

for a middle

Explain the mechanics: pick the witness of p opens then p closes, use the clause bounding the prefix to force the pumped block into the opening run, pump up, and show the counts diverge.

for a senior

Show the judgment: distinguish unbounded nesting from a capped depth, propose the cap or a counter-carrying validator, and say what the split of lexical screening from structural validation costs the team.

for a principal

Frame it as a format decision. A specification that admits unbounded nesting has just chosen a class of validator for every consumer that will ever parse it, in every language they use.

## What is actually being claimed A pattern in the regular-expression sense denotes a **regular language**: exactly the set of strings accepted by some finite automaton — a machine with a fixed, finite set of states, one transition per state and input symbol, and **no other memory**. The proposal under review claims that one such pattern accepts exactly the correctly balanced, arbitrarily nested bracket strings and rejects everything else. The refutation has to be strong enough to cover *every* pattern, not just the one on the screen; otherwise the colleague rewrites it and the review repeats. ## The lemma, stated in the direction you use it If a language `L` is regular, then there exists a **pumping length** `p >= 1` such that every string `s` in `L` with `|s| >= p` can be written `s = x y z` with: 1. `|y| >= 1` — the pumped block is non-empty; 2. `|xy| <= p` — the block sits inside the first `p` symbols; 3. `x y^i z` is in `L` for every `i >= 0` — repeating or deleting the block keeps you inside the language. The implication runs **regular implies pumpable**. You use the contrapositive: exhibit a string in `L` for which no legal decomposition survives pumping, and `L` cannot be regular. ## Choosing the witness so the case split collapses The whole craft is in the witness. Take `s` to be `p` opening brackets followed by `p` closing brackets. It is balanced, so it is in the claimed language, and its length `2p` is at least `p`, so the lemma applies. Now clause 2 does the work: because `|xy| <= p` and the first `p` symbols are all opening brackets, **`y` can only be a non-empty run of opening brackets** — there is no case where it contains a closing bracket and no case where it straddles the boundary. One case, not three. Pump to `i = 2`. Writing `k = |y|` with `k >= 1`, the result has `p + k` opening brackets and still `p` closing brackets. It is not balanced, so it is not in the language — yet clause 3 insists the machine accepts it. Contradiction; no finite automaton, and therefore no regular pattern, accepts exactly the balanced strings. ## Why depth is the thing a finite machine cannot hold The pumping argument is the formal shadow of a counting argument. To decide balance, a recogniser must, at the moment it meets the first closing bracket, distinguish "three opens are outstanding" from "four opens are outstanding" — and from every other count, without bound. A machine with `m` states, fed runs of `1, 2, ..., m + 1` opening brackets, must by the pigeonhole principle end two different runs in the **same** state. From that shared state it behaves identically forever, so it accepts a mismatched pair of counts. Pumping is exactly that repeated-state loop, made visible as a substring you may repeat. ## Bounded versus unbounded, in review terms | what the format requires | finite-state checkable | why | |---|---|---| | nesting capped at a fixed depth | yes | depth takes finitely many values, so finitely many states encode it | | total input length capped | yes | finitely many strings, which is trivially a regular set | | unbounded nesting, balanced | no | depth must be remembered without bound | | unbounded nesting, depth parity only | yes | parity is one bit, not a count | The last row is worth saying aloud in the review: it is not "brackets" that defeat the machine, it is **unbounded counting**. A weaker property of the same input can still be regular. ## What to put in the review comment Three honest outcomes. Cap the nesting depth in the specification and generate a pattern for that cap — the pattern grows with the cap, but it exists. Split the job: let the pattern do lexical screening and hand structural validation to code that carries a counter or a stack. Or change the input format so nesting is not expressible. What does **not** work is asking for a cleverer pattern: the argument above quantifies over all of them. One caveat worth stating precisely: if a tool's pattern language includes features beyond the regular operators, the thing you are running is no longer the object this argument is about — that is a change of tool and of cost model, not a cleverer pattern, and it should be reviewed as such.

  • Does finding one deeply nested input that the proposed pattern mishandles prove that no pattern can work?
    No. A failing input refutes that one pattern, and the author can answer it by adding another alternative. The pumping argument is different in kind: it starts from an arbitrary finite-state recogniser and derives a contradiction, so it rules out the entire class at once. Use the counterexample to open the conversation and the lemma to close it.
  • What changes if the specification caps nesting at a fixed depth?
    The language becomes regular. Depth then ranges over finitely many values, so a machine can hold the current depth in its state and a pattern exists. The cost moves from impossible to merely unpleasant: the pattern's size grows with the cap, and the cap becomes a contract the rest of the system must honour.
  • Why does the argument choose that particular witness rather than any long balanced string?
    Because the clause `|xy| <= p` only constrains the first `p` symbols. Leading with `p` opening brackets guarantees the pumped block is a run of opens, so a single case finishes the proof. A witness that mixes bracket kinds early forces a case analysis over every legal split, and one surviving case sinks the argument.

A doorman who must know when the building is empty, but owns only a fixed number of counters. Past his last counter he cannot tell seven people inside from eight, so he eventually waves out a crowd that was never fully inside.

saying these in an interview costs you the question

  • Claims a sufficiently clever pattern can match balanced brackets
  • Says the recogniser just needs more states, so a longer pattern works
  • Treats one failing test input as proof that no pattern exists
  • Confuses a fixed nesting cap with unbounded nesting
  • Assumes any feature an engine adds still keeps the language regular