parsing
programming
text analysis
algorithms
syntax highlighting

How do I find the position of matching parentheses or braces in a given piece of text?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Finding matching parentheses or braces is a classic parsing problem. Editors use it for bracket highlighting, compilers use it for syntax checks, and many text-processing tools need it when walking nested structures. The standard solution is a stack: push opening delimiters as you see them, then pop when the matching closing delimiter appears.

The Core Stack Idea

A stack works well because brackets are nested in last-opened, first-closed order.

Example:

text
a * (b + {c - d})

Reading left to right:

  • push the index of (
  • push the index of {
  • when } appears, match it with the most recent {
  • when ) appears, match it with the most recent (

That is exactly the access pattern a stack is built for.

Matching All Pairs In One Pass

If you want every matched pair in a string, scan the text once and keep indices of opening symbols on the stack.

python
1
2def find_matches(text: str):
3    opens = {"(": ")", "[": "]", "{": "}"}
4    closes = {v: k for k, v in opens.items()}
5
6    stack = []
7    pairs = []
8
9    for index, ch in enumerate(text):
10        if ch in opens:
11            stack.append((ch, index))
12        elif ch in closes:
13            if not stack:
14                raise ValueError(f"Unmatched closing {ch!r} at {index}")
15
16            open_ch, open_index = stack.pop()
17            if open_ch != closes[ch]:
18                raise ValueError(
19                    f"Mismatched {open_ch!r} at {open_index} and {ch!r} at {index}"
20                )
21
22            pairs.append((open_index, index))
23
24    if stack:
25        open_ch, open_index = stack[-1]
26        raise ValueError(f"Unmatched opening {open_ch!r} at {open_index}")
27
28    return pairs
29
30
31text = "a * (b + {c - d})"
32print(find_matches(text))

This returns index pairs for every matching delimiter.

Finding The Match For One Specific Position

Sometimes you already know one index and want only its partner. For example, a text editor may place the cursor on a ) and ask for the matching (.

A convenient approach is:

  • if the current character is an opening delimiter, scan forward
  • if it is a closing delimiter, scan backward
  • keep a depth counter for the same delimiter type
python
1
2def match_at(text: str, pos: int) -> int:
3    pairs = {"(": ")", "[": "]", "{": "}"}
4    reverse = {v: k for k, v in pairs.items()}
5    ch = text[pos]
6
7    if ch in pairs:
8        target = pairs[ch]
9        depth = 0
10        for i in range(pos, len(text)):
11            if text[i] == ch:
12                depth += 1
13            elif text[i] == target:
14                depth -= 1
15                if depth == 0:
16                    return i
17    elif ch in reverse:
18        target = reverse[ch]
19        depth = 0
20        for i in range(pos, -1, -1):
21            if text[i] == ch:
22                depth += 1
23            elif text[i] == target:
24                depth -= 1
25                if depth == 0:
26                    return i
27    else:
28        raise ValueError("Position does not contain a bracket")
29
30    raise ValueError("No matching bracket found")
31
32
33sample = "x = func(a, {b: [1, 2, 3]})"
34print(match_at(sample, sample.index("{")))

This is useful when you only care about one bracket around the cursor rather than every pair in the text.

Handling Multiple Bracket Types Correctly

The main bug to avoid is matching only by nesting depth without checking bracket type. ( must match ), { must match }, and [ must match ].

This should fail:

text
([)]

Even though the counts of opening and closing symbols look balanced, the nesting order is wrong. A proper stack-based parser catches that mismatch.

Real-World Complications

Real parsers often need more than raw bracket matching. You may need to ignore brackets that appear inside:

  • string literals
  • comments
  • escaped sections
  • template syntax

For example, the ( in a quoted string should not count as structure in many languages. If you are writing a full parser or syntax highlighter, bracket logic is often combined with tokenization so structural characters inside strings are skipped.

Still, the bracket-matching core is the same: tokenization first, stack logic second.

Time Complexity

The standard stack solution is efficient:

  • time complexity: O(n) for one full pass
  • space complexity: O(n) in the worst case for deeply nested openings

That is optimal for ordinary linear text scanning.

Common Pitfalls

  • Matching delimiters by simple counting instead of respecting nesting order.
  • Ignoring bracket type and accidentally allowing ( to match ].
  • Forgetting to report unmatched openings left on the stack after the scan.
  • Treating brackets inside quoted strings as real structure when the text format says otherwise.
  • Re-scanning the whole text repeatedly when one linear pass would be enough.

Summary

  • Use a stack to match parentheses, braces, and brackets correctly.
  • Push opening delimiters with their indices and pop them when matching closers appear.
  • Validate bracket type, not just nesting depth.
  • Use a forward or backward depth scan when you only need the match for one known position.
  • For real languages, combine bracket matching with tokenization so strings and comments are handled correctly.

Course illustration
Course illustration

All Rights Reserved.