LeetCode 20: Valid Parentheses in TypeScript & V8 Stack Mechanics

When software engineers transition from systems languages such as C++ or Java into JavaScript and TypeScript, one common conceptual friction point is the absence of dedicated collection classes like std::stack or java.util.ArrayDeque in the standard runtime library. Developers frequently ask: "How should I implement a stack without risking performance degradation?" In JavaScript engines like Google's V8, dynamic arrays are backed by contiguous memory buffers that provide amortized O(1) time complexity for push() and pop() operations. Consequently, solving classic abstract data structure problems like LeetCode 20: Valid Parentheses in TypeScript is not only straightforward, but also achieves native execution speeds rivaling compiled languages when memory layouts are handled correctly. Below is a deep dive into the parentheses matching invariant, V8 array internals, early exit pruning, and production implementations across TypeScript, C++20, and Python.

1. Problem Definition, Invariants & The LIFO Stack Principle

The problem presents a string s consisting exclusively of the bracket characters '(', ')', '{', '}', '[', and ']'. We must determine whether the string is structurally valid according to three fundamental rules:

  1. Every opening bracket must be closed by the identical type of closing bracket.
  2. Opening brackets must be closed in the exact reverse order of their appearance (strict nesting).
  3. Every closing bracket must match a previously unmatched, active opening bracket.

This strict hierarchical nesting is governed by the Last-In, First-Out (LIFO) principle. The most recently opened bracket is always the first bracket that must be closed when encountering a corresponding closing symbol. If an incoming closing bracket does not match the topmost unclosed bracket on our active stack, the entire sequence is immediately invalidated.

LIFO Stack Evaluation Trace: "([{}])" Input: '(' '(' Push '(' Input: '[' '(' '[' Push '[' Input: '{' '(' '[' '{' Push '{' Input: '}' '(' '[' ✓ Match '{' Pop '{' Finish Stack Empty TRUE len === 0

2. V8 JavaScript Engine Internals: Why JS Arrays Are Great Stacks

In ECMAScript engines like V8 (Node.js, Chromium) and SpiderMonkey (Firefox), arrays are not naive hash tables or linked lists. When an array is instantiated as an empty literal const stack = [] and populated with uniform single-character strings, V8 optimizes it into an internal element kind known as PACKED_ELEMENTS.

  • Amortized O(1) Push Operations: V8 allocates backing store buffers with exponential headroom. When you invoke stack.push(char), the element is placed directly into the next free slot in the contiguous heap buffer and the length pointer is incremented in constant time. When capacity is exceeded, V8 doubles the buffer and copies existing pointers, maintaining an amortized O(1) time bound.
  • O(1) Pop Operations: Invoking stack.pop() simply decrements the internal array length property and returns the reference at the previous top index. The underlying memory is not immediately reallocated, ensuring instantaneous O(1) deallocation.
  • Caution Against Shift/Unshift: Operating on the front of a JavaScript array using shift() or unshift() forces the runtime engine to rewrite memory offsets for every subsequent element, incurring a brutal O(N) performance penalty. As long as you treat the array tail (push and pop) as the stack top, performance is optimal.

3. Performance Optimizations: Early Exit Pruning & Map Lookups

To write high-performance parsing code, two immediate optimizations should be applied before allocating memory:

  1. Odd-Length Parity Check: Every valid parentheses sequence must consist of matched pairs. If the length of the string s.length % 2 !== 0, it is mathematically impossible to pair every bracket. We can immediately return false in O(1) time without examining a single character.
  2. Lookup Mapping: Rather than writing deeply nested if/else chains checking every opening and closing combination, map each closing bracket to its corresponding opening bracket in a lookup hash map or Record<string, string>. When a character exists as a key in the map, it is a closing bracket; if it does not, it is an opening bracket that must be pushed onto the stack.

4. Production-Grade Implementations

Below is the complete, strictly typed TypeScript solution alongside equivalent high-performance C++20 and Python 3 implementations.

TypeScript Implementation

/**
 * LeetCode 20: Valid Parentheses
 * Time Complexity: O(N) | Auxiliary Space: O(N)
 */
export function isValid(s: string): boolean {
  // Optimization 1: Odd lengths can never form balanced pairs
  if (s.length % 2 !== 0) {
    return false;
  }

  // Lookup map: Closing bracket -> Expected opening bracket
  const bracketMap: Record = {
    ')': '(',
    '}': '{',
    ']': '['
  };

  const stack: string[] = [];

  for (let i = 0; i < s.length; i++) {
    const char = s[i];
    const expectedOpen = bracketMap[char];

    if (expectedOpen !== undefined) {
      // Current char is a closing bracket: inspect top of stack
      const top = stack.pop();
      if (top !== expectedOpen) {
        return false;
      }
    } else {
      // Current char is an opening bracket: push onto stack
      stack.push(char);
    }
  }

  // If stack is completely drained, all brackets matched in proper order
  return stack.length === 0;
}

Modern C++20 Implementation

#include <string>
#include <stack>
#include <string_view>

class Solution {
public:
    bool isValid(std::string_view s) {
        if (s.size() % 2 != 0) return false;

        std::stack<char> st;

        for (char c : s) {
            switch (c) {
                case '(': st.push(')'); break;
                case '{': st.push('}'); break;
                case '[': st.push(']'); break;
                default:
                    // If closing bracket, verify non-empty stack and match
                    if (st.empty() || st.top() != c) {
                        return false;
                    }
                    st.pop();
                    break;
            }
        }

        return st.empty();
    }
};

Python 3 Implementation

class Solution:
    def isValid(self, s: str) -> bool:
        if len(s) % 2 != 0:
            return False

        mapping = {')': '(', '}': '{', ']': '['}
        stack = []

        for char in s:
            if char in mapping:
                top_element = stack.pop() if stack else '#'
                if mapping[char] != top_element:
                    return False
            else:
                stack.append(char)

        return len(stack) == 0

5. Asymptotic Complexity Analysis

  • Time Complexity: O(N): We iterate through the input string of length N exactly once. In each iteration, hash map lookups, array pushes, and array pops execute in amortized O(1) time. Early exit bounds provide instantaneous O(1) rejection for odd-length strings and early mismatches.
  • Auxiliary Space Complexity: O(N): In the worst-case scenario (e.g., a string consisting entirely of opening brackets like "(((((("), the stack stores up to N characters before termination. In the average case of balanced interleaved brackets, the maximum stack depth is bounded by the maximum nesting depth O(D), where D ≤ N / 2.

6. Edge Cases & Defensive Testing Scenarios

A resilient implementation must pass rigorous edge cases beyond standard textbook examples:

  • Empty String (""): Returns true vacuously as zero unmatched brackets exist.
  • Single Character Strings ("(" or "]"): Filtered immediately by the odd-length parity check.
  • Right-Heavy Sequences ("())" or "}"): Pops on an empty stack are detected gracefully without runtime exceptions.
  • Interleaved Mismatches ("([)]"): Correctly fails because ']' encounters '(' at the stack top instead of its required counterpart '['.
  • Deeply Nested Sequences ("((((...))))"): Handles stack depths of $10^5$ elements without exceeding V8 call stack limits because iteration utilizes heap-allocated array memory rather than recursive function frames.