skip to content

questions

9

Why does validating nested delimiters of several types need a stack rather than a counter?

level: juniorimportance: must knowfreq 80%

answer

  1. Think about what one integer can record
  2. Balance is a count; nesting is order
  3. Try a sequence where the pairs overlap
  4. Which opener is still waiting, not how many
  5. Last opened must be first closed

basics

~20 s

A counter records how many delimiters are open, not which ones. With several delimiter types you must know the type of the most recently opened one to reject overlapping pairs, and last-opened-first-closed is exactly what a stack gives you.

solid answer

~50 s

With a single delimiter type a counter really is enough: increment on an opener, decrement on a closer, reject if it ever goes negative or ends non-zero, in `O(1)` space. The moment a linter has to handle several types, counting stops working, because nesting is a question about order, not quantity. The sequence `( [ ) ]` leaves every per-type count at zero yet is malformed: the parenthesis closes while a bracket is still open. To reject it you need to know, at the closer, that the most recently opened unclosed delimiter is `[` and not `(` — last in, first out, which is the stack contract. So: push openers, on a closer pop and compare types, and accept only if the stack is empty at end of input. `O(n)` time, `O(d)` space for nesting depth d. A stack also wins with one type if you must report *where* the unclosed opener was.

go deeper

for a junior

Be ready to state the rule out loud: push openers, on a closer pop and compare types, accept only if nothing is left at the end. Know the two rejection cases — a closer with nothing open, and an opener never closed.

for a middle

Explain why counting fails: produce an overlapping sequence where every count balances yet the nesting is wrong, and connect last-opened-first-closed to the LIFO contract. State the cost as linear time and depth-proportional space.

for a senior

Show the production layer: a state machine so delimiters inside strings, escapes and comments never reach the stack, positions stored alongside openers for precise diagnostics, and error recovery so one mismatch does not abort the whole file.

for a principal

Own the judgment about when the simple structure stops being enough. Argue when a hand-rolled checker is right versus a real grammar-driven parser, and what it costs a team to maintain scanner state machines across many input formats.

## What is really being asked Delimiter matching is the first application of a stack most people meet, and it is also where an over-generalisation takes root: "any balance check needs a stack." That is false. Knowing precisely when the stack earns its place is what separates an understood answer from a memorised one. ## The one-type case: a counter is enough Suppose a config linter only has to verify that a file's parentheses balance. Scan left to right, add one on an opener, subtract one on a closer. Two rules decide the file: - if the counter ever goes **negative**, a closer appeared with nothing open — reject immediately, at that position; - if the counter is **non-zero at end of input**, some opener was never closed — reject. Otherwise accept. Time `O(n)`, extra space `O(1)`. A stack would also work, but in a deeply nested file it stores up to n/2 identical entries to answer a question one integer answers. With a single type, "which opener is on top?" has only one possible answer, so the stack carries no information the counter lacks. Notice the asymmetry that already appears, though. The counter tells you *that* the file is unbalanced, not *where*. If the linter must emit "unclosed delimiter opened at line 42, column 7", it needs somewhere to keep the position of every pending opener — and that is a stack again, even with one type. The structure follows what you must *report*, not only what you must decide. ## Several types: the counter breaks Now the linter handles round, square and curly pairs plus a pair of typographic quotation marks. Consider the sequence `( [ ) ]`. Each type appears exactly once open and once closed, so every per-type counter ends at zero and no total ever goes negative. The input is nonetheless malformed: the parenthesis closes while the bracket is still open, so the two regions **overlap** instead of nesting. That is the whole point. Counting answers "how many are open". Nesting is a claim about **order**: the region opened most recently must be the region closed first. To reject `( [ ) ]` you must know, at the moment the closer arrives, that the most recently opened and still-unclosed delimiter is the square bracket. "Most recently opened, first to be closed" is last-in-first-out stated in the problem's own vocabulary — which is why the stack is not a clever trick here but a restatement of the requirement. ## The algorithm Scan the input one delimiter at a time. - **Opener** — push it, together with its position if you need to report one. - **Closer** — if the stack is empty, reject: a closer with nothing open. Otherwise pop; if the popped opener is not this closer's partner, reject with both offenders named. - **End of input** — accept if and only if the stack is empty. Anything left is unclosed, and the bottom-most entry is the outermost unclosed region. Each delimiter is pushed and popped at most once, so the scan is `O(n)` time and `O(d)` space where d is maximum nesting depth (`O(n)` worst case, small in practice). ## What a real linter adds on top Real inputs are not pure delimiter soup, and this is where interview answers thin out. - A delimiter **inside a quoted string** is text, not structure. A closing bracket in a message string must not pop anything. - **Escapes** suppress the next character's structural meaning; **comments** hide delimiters entirely. So the scanner is a small state machine — in-code, in-string, in-comment — and only the in-code state feeds the stack. - **Text encoding** matters for error messages: with multi-byte characters, the position you record should be a character or grapheme index, not a byte offset, or the caret in the reported error lands in the wrong column. - Some pairs cannot be matched by shape at all. A **symmetric** delimiter, where the same character opens and closes, carries no direction, so a stack cannot tell an opener from a closer; you toggle a flag instead, or rely on typographic pairs that genuinely differ. Another case where the stack is the wrong instrument. ## The transferable lesson A stack is the right structure when you are tracking **pending obligations that must be discharged in reverse order of arrival**. Nested delimiters, nested markup elements, nested scopes, nested calls — all the same shape. When there is only one kind of obligation and you only need a yes/no verdict, a counter is a cheaper realisation of the same idea. When the obligations differ in kind, or you must name the one still outstanding, only the stack retains that. The interview failure mode is reaching for the stack reflexively and being unable to say what it buys. The strong answer names the counter case, names the input that breaks it, and names LIFO as the reason the stack fits.

  • With a single delimiter type, when would you still choose a stack over a counter?
    When the report matters, not just the verdict. A counter can say a file is unbalanced but not where the offending opener was; pushing each opener with its line and column lets the linter point at the exact unclosed one. You also need the stack if you must recover and keep checking the rest of the file rather than stopping at the first error.
  • How does a linter avoid counting delimiters that appear inside quoted strings or comments?
    By scanning with a small state machine rather than looking at characters in isolation. The scanner tracks whether it is in code, in a string literal, or in a comment, and only in the code state do delimiters reach the stack. Escape sequences suppress the next character's structural meaning. Skipping this is the classic reason a naive checker reports a false error on a message string.
  • What does the stack contain at the moment the checker reports a mismatch?
    Every opener seen so far that has not yet been closed, in the order they were opened, with the most recent on top. That makes a precise diagnostic possible: the top entry is the delimiter that should have been closed, the incoming closer is what actually arrived, and the bottom entry is the outermost region still open.

A counter is a tally of how many doors you have opened; a stack is the ring of keys in your hand, so you can check that the door you are closing is the one you opened last.

saying these in an interview costs you the question

  • Claims every balance check requires a stack
  • Thinks equal open and close totals prove correct nesting
  • Forgets to reject a closer when nothing is open
  • Forgets to check the stack is empty at end of input
  • Counts delimiters that appear inside quoted strings
  • Cannot say what the stack stores that a counter cannot

context

open as a page

Why can a linked-node stack hold millions of elements when a thread's call stack overflows far sooner?

level: middleimportance: must knowfreq 62%

basics

~20 s

They are different things. A stack data structure is a LIFO discipline over memory you allocate on demand, so its ceiling is available memory. A thread's call stack is one fixed contiguous region reserved when the thread starts.

open as a page

In iterative depth-first traversal with an explicit stack, why push children in reverse order?

level: middleimportance: should knowfreq 40%

basics

~20 s

A stack returns the most recent push, so pushing children left to right makes the rightmost child come out first. Pushing them right to left puts the leftmost on top, which reproduces the order recursion visits siblings in.

open as a page

In a stack-based postfix evaluator, why must the first value popped be the right operand?

level: middleimportance: should knowfreq 52%

basics

~20 s

Operands are pushed left to right, so the top of the stack is the right-hand operand and the value beneath it is the left. Reversing the two pops silently inverts subtraction and division while sums and products still look correct.

open as a page

In an array-backed stack, what changes in push, pop and the empty check if `top` means the next free slot?

level: middleimportance: should knowfreq 52%

basics

~20 s

Two conventions exist: top indexes the last element (empty is top == -1, push increments then writes), or top indexes the next free slot (empty is top == 0, push writes then increments). Pick one and check every operation against it.

open as a page

In a two-stack undo/redo design, why must a new user action clear the redo stack?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Redo entries are only meaningful against the exact state they were undone from. A new action rewrites history from that point, so replaying them would target objects that no longer exist. Clearing the redo stack keeps the model honest.

open as a page

What should pop on an empty stack do — signal an error, or return a sentinel value like -1?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Signal the failure out of band. A sentinel is safe only when it lies outside the element domain, and for signed sensor readings -1 is a legal reading, so the caller cannot tell emptiness from data and the corruption is silent.

open as a page

On a memory-capped device, when a fixed-capacity stack fills, do you reject the push or grow the stack?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

On memory-capped hardware, reject the push and make the rejection visible. A fixed capacity turns an unpredictable out-of-memory failure into a local, testable policy — provided the caller is told and someone has decided which readings may be lost.

open as a page