Skip to main content
2 of 2
Changed explanation

Julia, 51 bytes

~z=z==(n=replace(z,r"\(\)|\[]|{}|<>",""))?z=="":~n

The least insane of several options. Unsurprisingly, leveraging the power of regex is the shortest path to string matching, but this really only applies if the pattern to match is regular. Trying to do PCRE recursive patterns ends up ballooning the size of the code, either by seeing if the whole string is the match or by anchoring the ends and then creating a construct to specify the inner body for regex recursion. Neither of which are pretty or conducive to code golf.

Explanation:

~z=                            # Define ~z to be the following:
    z==(                       # If z is equal to                                     
        n=replace(z,           # z with the replacement of 
            r"\(\)|\[]|{}|<>", # adjacent matching brackets ((),[],{}, or <>)
            ""                 # with empty strings
        )                      # (which is assigned to n)
    )?z==""                    # whether z is an empty string
    :~n                        # else ~ applied to the substituted string

The function repeatedly removes adjacent pairs of parentheses from its only argument, and returns true if it can derive an empty string this way.