Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Coding Interview Patterns  Module 16 – Backtracking

Generate parentheses: brackets and expressions

Generate every balanced bracket string and every operator expression that reaches a target: both problems, a measured brute force and the signal for search.

  • Advanced
  • 35 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free intro, Pro: 8 of 35 minutes free
Free: Two structures to generate · The brute force, and why it fails at these sizes · What to notice. With a pass: the other 14 sections and 3 interview answers, about 27 minutes.

What you will learn

  • Generate balanced bracket strings with one counter for opening and one for closing brackets
  • Prove that every prefix the search keeps can be completed, so the search never meets a dead end
  • Build expressions between digits that reach a target, carrying the last term so that * binds first
  • Handle operands with leading zeros and values past 32 bits in five languages
  • Find the k-th balanced string by counting completions, without listing the others

Before you start

What you’ll learn · Before you start

What you will learn

  • Generate balanced bracket strings with one counter for opening and one for closing brackets
  • Prove that every prefix the search keeps can be completed, so the search never meets a dead end
  • Build expressions between digits that reach a target, carrying the last term so that * binds first
  • Handle operands with leading zeros and values past 32 bits in five languages
  • Find the k-th balanced string by counting completions, without listing the others

Before you start

On this page

To generate parentheses is to list every balanced string of n opening and n closing brackets. Backtracking builds such strings one character at a time and never writes a prefix that cannot be finished. The same idea generates other valid structures, such as every way to put operators between digits so that the expression reaches a target. Here are both problems, a brute force with its cost measured, and the signal that points to the search.

Two structures to generate

Problem A: balanced layouts

A layout engine nests n boxes. A layout is a string of n ( and n ), read left to right, in which no prefix has more ) than (. Write layouts(n), which returns every layout in lexicographic order, ( before ). Limits: 1 ≤ n ≤ 12.

For n = 1 the only layout is (); n = 2 gives (()) and ()(); n = 3 gives five, from ((())) to ()()().

Problem B: operators between digits

A meter shows a string of digits. Put +, -, * or nothing between each pair of neighbouring digits, so that the expression, with * before + and -, equals a target. Write reach_target(digits, target), which returns every such expression. No operand may start with 0 unless it is 0. Limits: 1 to 10 digits.

31415 reaches 17 in two ways, 3*1*4+1*5 and 3*1*4*1+5; 2048 reaches 8 as 2*0*4+8 and 20-4-8. 2005 reaches 7 in six ways, and 2+005 is not one of them.

The tree of bracket prefixes for n = 2: two paths end in (()) and ()(), and three dashed prefixes can never be completed.Prefixes for n = 2start()((()((((()()(())(())()()dashed: a prefix that can never be completed;filled: the two balanced layouts

The prefixes for n = 2; dashed ones can never be completed

Text description of the diagram

The tree starts from the empty string at the top. Each box below a box adds one bracket.

  • From the start, ( leads on, and ) is dashed: a closing bracket with nothing open can never be part of a balanced layout.
  • From (, both (( and () lead on.
  • From ((, a third ( is dashed, because n = 2 allows only two opening brackets; (() leads on, and then (()) completes the first layout.
  • From (), a closing bracket is dashed again, because nothing is open; ()( leads on, and then ()() completes the second layout.

The two filled boxes, (()) and ()(), are the only balanced layouts of two pairs. Every dashed box is a prefix that no continuation can fix, so nothing below it needs to be built.

The brute force, and why it fails at these sizes

The brute force builds every string and keeps the ones that pass a check: all 4ⁿ strings of 2n brackets, and all 4 to the power (digits − 1) ways to fill the gaps between digits, each evaluated by Python’s eval.

Every string built, then checked Python · brute/brute_force.py
# The brute force for both problems: build every string, then keep the ones that pass the check.
import itertools
import re
import time


def balanced(s):
    depth = 0
    for ch in s:
        depth += 1 if ch == "(" else -1
        if depth < 0:
            return False
    return depth == 0


def layouts_brute(n):
    """Every string of 2n brackets, kept when it is balanced."""
    return ["".join(p) for p in itertools.product("()", repeat=2 * n) if balanced("".join(p))]


LEADING_ZERO = re.compile(r"(?<!\d)0\d")


def expressions_brute(digits, target):
    """Every way to put +, -, * or nothing between the digits, each evaluated from scratch."""
    found = []
    for ops in itertools.product(["", "+", "-", "*"], repeat=len(digits) - 1):
        expr = digits[0] + "".join(op + d for op, d in zip(ops, digits[1:]))
        if LEADING_ZERO.search(expr):
            continue  # 05 is not a number here, and eval would raise SyntaxError on it
        if eval(expr) == target:
            found.append(expr)
    return found


print(layouts_brute(3))
print(" n  layouts  strings tried   time")
for n in range(1, 10):
    start = time.perf_counter()
    count = len(layouts_brute(n))
    seconds = time.perf_counter() - start
    print(f"{n:2}  {count:7,}  {4 ** n:13,}  {seconds:5.2f} s")
print(f"n = 12: {4 ** 12:,} strings, about {seconds * 4 ** 3 * 12 / 9:.0f} s at the speed of the last row")

print(expressions_brute("31415", 17), expressions_brute("2048", 8))
start = time.perf_counter()
count = len(expressions_brute("31415926", 100))
seconds = time.perf_counter() - start
print(f"8 digits: {4 ** 7:,} expressions evaluated, {count} equal 100, {seconds:.2f} s")
print(f"10 digits: {4 ** 9:,} expressions, about {seconds * 16 * 10 / 8:.0f} s at that speed")

Output

['((()))', '(()())', '(())()', '()(())', '()()()']
 n  layouts  strings tried   time
 1        1              4   0.00 s
 2        2             16   0.00 s
 3        5             64   0.00 s
 4       14            256   0.00 s
 5       42          1,024   0.00 s
 6      132          4,096   0.00 s
 7      429         16,384   0.01 s
 8    1,430         65,536   0.03 s
 9    4,862        262,144   0.26 s
n = 12: 16,777,216 strings, about 22 s at the speed of the last row
['3*1*4+1*5', '3*1*4*1+5'] ['20-4-8', '2*0*4+8']
8 digits: 16,384 expressions evaluated, 21 equal 100, 0.90 s
10 digits: 262,144 expressions, about 18 s at that speed

This output changes from run to run: the times depend on the computer and on what else it is doing

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brute_force.py

The answers are right, and the waste is easy to see. For n = 9 only 4,862 of 262,144 strings are balanced, under 2 %, and n = 12 means 16,777,216 strings for 208,012 layouts. The expressions are worse: each is built and parsed from scratch, so 10 digits mean 262,144 calls to eval, which the measured rate puts at seconds, not milliseconds.

What to notice

  • A prefix can already be hopeless. A prefix with more ) than (, or with more than n (, can never become a layout, so a search that grows the string one character at a time can refuse it on the spot.
  • Expressions share their beginnings. Every expression starting 3*1*4 repeats the same work up to that point, so carry the value forward instead of evaluating each string again.
  • Precedence is the one difficulty. In 1+2*3, the * changes a term that has already been added.

Four techniques follow:

  • Two counters, for opening and closing brackets, decide which character may come next.
  • The running value and the last term let * replace the last term by its product.
  • The leading-zero rule stops an operand from growing once it starts with 0.
  • Counting completions finds the k-th layout directly, without listing the ones before it.

Balanced layouts with two counters Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Why the search never meets a dead end
  • Expressions with the last term carried
  • Why carrying the last term respects precedence
  • Measured against evaluating every string
  • The code in five languages
  • How much work it does
  • Edge cases and common bugs
  • Variants: the k-th layout, two kinds of brackets, and counting
  • Follow-up questions
  • Exercise: operators that reach a target
    • Put operators between the digits to reach a target
  • A practice set with sample answers
  • Check yourself
  • Interview questions
    • When a search generates balanced strings of n pairs of parentheses, when may it add an opening bracket, and when a closing one?
    • An API returns the balanced strings of n = 16 pairs of brackets one page of 50 at a time, in lexicographic order, and may not keep the whole list in memory. How do you serve page p quickly, for any p?
    • You must list every way to put +, - or * between the digits of a string so that it equals a target, without calling eval. A colleague's version multiplies the running total when it adds a *. Show an input it gets wrong, fix it, and say what else breaks with ten digits in Java.
    • The same 12-digit meter reading is queried with 10,000 different targets a second, and each query asks how many expressions with +, - or * between the digits reach the target. What do you precompute, how much memory does it take, and how is a query answered?
  • Key takeaways

8 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 22 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.