Use a stack and scan the string from left to right. Push each opening bracket; for every closing bracket, require the matching opener at the top of the stack and then pop it. The input is valid only if no mismatch occurs and the stack is empty at the end.
The standard stack algorithm
Balanced delimiters follow a last-in, first-out rule. The most recently opened bracket must be the first one closed. A Python list provides exactly the operations needed: append() pushes an opener and pop() removes the most recent opener.
Runnable implementation
def valid_parentheses(text: str) -> bool:
matching = {")": "(", "]": "[", "}": "{"}
stack: list[str] = []
for char in text:
if char in "([{":
stack.append(char)
elif char in matching:
if not stack or stack[-1] != matching[char]:
return False
stack.pop()
else:
raise ValueError(f"unexpected character: {char!r}")
return not stack
The function accepts a string and returns a Boolean. It raises ValueError for characters other than brackets because the implementation treats the input as a bracket sequence. That policy is deliberate: decide whether non-bracket text is invalid or should be ignored before choosing your version.
How each character is processed
- An opening bracket—
(,[, or{—is appended tostack. - A closing bracket looks up its required opener in
matching. - If the stack is empty, the closing bracket has nothing to close, so the function returns
False. - If the top stack item differs from the required opener, the nesting order is wrong and the function returns
False. - When the top item matches, it is popped.
- After the scan,
not stackis true only when every opener has been closed.
Why the stack catches every invalid arrangement
Consider ([{}]). The stack evolves as [(], then [(, [], then [(, [, {]. The next character is }, which correctly matches {; the remaining closers then match [ and ( in reverse order.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
#1 Best Overall
For ([)], the stack top is [ when ) arrives. Because ) requires (, the function fails immediately. For )(, the first character finds an empty stack. For ((, no mismatch occurs while scanning, but the final stack still contains two openers, so the result is false.
Expected results for common inputs
| Input | Result | Reason |
|---|---|---|
()[]{} |
True |
Each opener closes with the correct type. |
([{}]) |
True |
Nested brackets close in reverse opening order. |
(] |
False |
The closing bracket has the wrong type. |
([)] |
False |
The closing order violates nesting. |
)( |
False |
A closing bracket appears before any opener. |
(( |
False |
Open brackets remain unmatched. |
|
True |
An empty sequence is balanced under the usual definition. |
Choose a policy for non-bracket characters
The strict function above raises an exception for input such as a(b). That is appropriate when a caller promises that the input contains only delimiters and you want malformed input reported explicitly. Text parsers usually need a different contract: ignore ordinary characters and validate only the brackets.
Ignoring ordinary text
def valid_parentheses_in_text(text: str) -> bool:
matching = {")": "(", "]": "[", "}": "{"}
stack: list[str] = []
for char in text:
if char in "([{":
stack.append(char)
elif char in matching:
if not stack or stack[-1] != matching[char]:
return False
stack.pop()
return not stack
With this contract, valid_parentheses_in_text("a(b)[c]") returns True, while valid_parentheses_in_text("a(b]") returns False. Characters such as quotes, angle brackets, or Unicode symbols are not treated as delimiters unless you add them explicitly. If quoted strings can contain bracket characters, a real lexer must first determine whether those characters are inside a string literal; the simple function cannot infer that context.
List versus deque
A list is the clearest default because every operation happens at one end. Python’s list methods are designed to make a list a last-in, first-out stack. collections.deque is also valid and offers approximately constant-time appends and pops at either end. Choose it when the surrounding parser already needs efficient operations on both ends; it does not make this single-ended algorithm faster.
Rank #2
| Criterion | List stack | deque |
|---|---|---|
| Bracket types | Any types represented in the mapping | Any types represented in the mapping |
| Non-bracket handling | Strict or ignoring, depending on the loop | Strict or ignoring, depending on the loop |
| Failure behavior | Can return immediately on the first mismatch | Can return immediately on the first mismatch |
| Time complexity | O(n) | O(n) |
| Worst-case auxiliary space | O(n) | O(n) |
| Readability for this task | Usually simplest | Useful when both ends are needed elsewhere |
Complexity and memory behavior
Let n be the input length. The scanner examines each character at most once, performs constant-time dictionary and list operations, and stops as soon as it finds a mismatch. Its worst-case running time is O(n). A string containing only opening brackets can leave all n characters on the stack, so worst-case auxiliary space is O(n). Inputs that close quickly may use less memory, but the asymptotic bound remains O(n).
For very large data that arrives as a stream, keep the same stack and process chunks rather than concatenating the entire input. The state between chunks is simply the current stack; a mismatch still allows immediate termination. A final nonempty stack means the stream ended with unclosed brackets.
Testing the function
Small executable checks
cases = {
"()[]{}": True,
"([{}])": True,
"(]": False,
"([)]": False,
")": False,
"(": False,
"": True,
}
for value, expected in cases.items():
assert valid_parentheses(value) is expected, value
try:
valid_parentheses("a(b)")
except ValueError:
pass
else:
raise AssertionError("strict mode should reject non-bracket text")
Useful edge cases
- Test each supported bracket type by itself:
(),[], and{}. - Test deep nesting to verify that the final stack is emptied correctly.
- Test a close before an opener, such as
]. - Test a wrong type at the deepest point, such as
({]}. - Test a missing final closer, such as
{[. - Test ordinary text under both policies so callers know whether an exception or a Boolean is expected.
Troubleshooting common mistakes
Checking only the counts
Counting opening and closing characters is insufficient. (] has one opener and one closer but is invalid because the types differ. Always compare the closing character with the stack top.
Using the first opener instead of the latest
Removing with pop(0) or treating the stack as a queue breaks nested input. Use stack[-1] to inspect and stack.pop() to remove the latest opener.
Recommended Free Tools
Forgetting the empty-stack check
Evaluating stack[-1] before checking whether the stack is empty raises IndexError for inputs such as ). Keep not stack in the condition first, as in the implementation above.
Returning true after the loop unconditionally
An input such as (( never encounters a mismatched closer, yet it is invalid. Return not stack, not True.
Accidentally accepting unsupported symbols
If your application supports only three bracket types, do not silently classify angle brackets or other punctuation as valid. Either reject them in strict mode or ignore them under a clearly documented text policy.
Or skip the browser setup
If you are generating screenshots for documentation, test fixtures, or visual checks, ScreenshotNeo returns a screenshot or PDF from one GET request. It accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be disabled. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and each response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteUse the ScreenshotNeo API documentation for the complete option list. The API supports full-page captures with lazy images, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper sizes and page ranges, custom CSS and JavaScript, pre-capture clicks, hidden selectors, selector or network-idle waits, request and resource blocking, custom headers and cookies, user agents and authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of 100 URLs per call, a usage API, and an OpenAPI specification. Common parameter names from other screenshot APIs also work.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan, and yearly billing provides two months free. Create a free ScreenshotNeo account to get started.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.FAQ
Does an empty string count as valid?
Yes. Under the conventional balanced-sequence definition, there are no unmatched brackets in an empty string, so the function returns True.
Can this validate Python syntax?
No. It checks delimiter nesting only. Python syntax also depends on names, indentation, operators, strings, comments, and grammar; use Python’s parser when syntax validation is required.
How can I support another delimiter pair?
Add the opener to the opening-character test and add its closing character and required opener to the mapping. Also update tests and document whether the new symbols may appear inside quoted text.
Best Value
Frequently Asked Questions
Should I use a regular expression instead of a stack?
A regular expression is not a natural fit for arbitrary nesting. The stack directly represents the nesting state and has a predictable O(n) scan.
Can the validator report the position of the error?
Yes. Iterate with enumerate(text) and return or raise a result containing the current index when a mismatch occurs; retain the final length as the position for an unclosed opener.
Is the function safe to call from multiple threads?
Yes, when each call creates its own local stack as shown. Do not share a mutable stack between calls unless you add synchronization and intentionally maintain shared parser state.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minuteQuick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

