A backtracking engine explores one path at a time. When a path fails it returns to the last choice it made and takes the other branch. That is cheap when there are a few choices and ruinous when the number of choices multiplies.
The multiplying happens when there is more than one way to divide the same text between the same parts of a pattern. (a+)+ against a run of a's can split it as one group of four, or two and two, or one and three, or four groups of one — and it will try every division before it admits the pattern does not fit.
Two shapes cause almost all of it. A quantifier inside a quantifier, as above. And two branches of an alternation that can match the same text, inside a quantifier — (a|a)+, or the far more common (\s|\s*)+ and (\w+|\d+)*.
Neither shape is wrong on its own. It is the combination with a failing tail that turns a pattern into a hang, which is why these are so hard to spot in review: the pattern works on every input anybody tried.
The fix is almost never a cleverer quantifier. It is saying the thing you actually meant, so that there is only one way to divide the text. ^a+$ accepts exactly what ^(a+)+$ accepts, and there is nothing for it to give back.
Where the pattern is genuinely about a delimiter, a negated class does the same job: "[^"]*" cannot backtrack into the quotes, because the class excludes them.