Validating Parentheses Input Using TypeScript

The Valid Parentheses Problem is a common interview‑type problem but isn't just academic; it also arises fairly frequently in real‑world development scenarios (although not always specifically in coupling parenthesis types), particularly when it comes to lower‑level interpreters or linting tools.
By ensuring that all opened brackets, braces, and parentheses are properly closed in the correct order, you add another layer of robustness to your code.
Understanding the Problem
The task itself is straightforward: Given a string containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid.
An input string is valid if:
- Open brackets are closed by the same type of brackets. And...
- Open brackets are closed in the correct order.
For example, {}, (), or [] are valid whilst (], [), or {[} would not be.
There is an additional layer of complication to this task in that nesting of parenthesis types should also be handled. It is valid to have one type nested inside another, as long as they open and close in the correct consecutive order.
Some Examples
Input: s = "()"
Output: true
Input: s = "(]"
Output: false
Input: s = "{[]}"
Output: true
Input: s = "{[}]"
Output: false
How We Can Solve It
The most straightforward way to tackle this problem is to use a stack data structure. In this context, a stack is a Last‑In, First‑Out (LIFO) data structure where the last element added is the first one to be removed. You could visualise it as being akin to a stack of books; you can only add or remove a book from the top of the stack.
For matching opening and closing brackets, this data structure is a really natural fit. When you encounter an opening bracket, you 'push' it onto the stack, and when you encounter a closing bracket, you 'pop' the last element from the stack to see if they match. By using a stack, we can easily keep track of the brackets and validate them simply and efficiently.
In Code
Here's how you could implement this solution method using TypeScript:
const isValid = (s: string): boolean => {
const stack: string[] = [];
const map: { [key: string]: string } = {
')': '(',
'}': '{',
']': '[',
};
for (const char of s) {
if (['(', '{', '['].includes(char)) {
stack.push(char);
} else {
const topElement = stack.pop();
if (map[char] !== topElement) {
return false;
}
}
}
return stack.length === 0;
};How the Code Works
- We initialise an empty stack and a mapping of closing and opening brackets.
- We then iterate through the provided string.
- When an opening bracket is encountered, it is pushed onto the stack.
- When a closing bracket is encountered, we check if its corresponding opening bracket is at the top of the stack.
- At the end, if the stack is empty, the string is valid.
Alternative Solution Methods
There is another way to view the problem: keep removing adjacent matching pairs until either the string is empty or no pair remains. It is useful for comparison, but repeated scans and string creation can cost more than the single stack pass.
Repeated Adjacent‑Pair Removal
Remove adjacent (), [] and {} pairs, then repeat. Nested pairs become adjacent as their inner contents disappear. An empty final string is valid; a non‑empty string with no removable pair is not. This can be written iteratively or recursively, and may take quadratic time on deeply nested input.
Why Counters are Not Enough
For a single bracket type, a counter can work: it must never go negative, and it must finish at zero. Separate counters for mixed bracket types lose nesting order. For example, ([)] balances each count but is invalid. We need to remember which opener is waiting to close.
Where Two Pointers Fit
I like the two‑pointer approach for problems such as valid palindromes and 3Sum Closest. Mixed nested brackets need a different invariant: comparing the outer positions does not tell us which opening bracket must close next. I would use the stack here.
The stack records that order directly. It visits the input once and needs space for the unmatched opening brackets, giving O(n) time and up to O(n) extra space.
Conclusion
The useful part of this exercise is recognising the order we need to preserve. A stack gives us the most recent opening bracket whenever a closing one arrives. Keep the mixed‑nesting cases in the tests; merely counting each bracket type would miss them.