C.W.K.
Stream
Lesson 10 of 10 · published

Performance and Alternatives

~8 min · performance, alternatives

Level 0Pattern-Curious
0 XP0/90 lessons0/15 achievements
0/100 XP to next level100 XP to go0% complete

Lookaround isn't free

Each lookaround is a sub-pattern that the engine evaluates independently at every relevant position. Patterns with multiple lookarounds can be slower than equivalent capture-and-test patterns, especially on long inputs.

When the engine has to backtrack

Lookarounds combined with greedy quantifiers can multiply backtracking. (?=.*X)(?=.*Y).* against an input that ALMOST has X and Y will trigger a lot of work as the engine confirms each lookahead and then tries the main pattern.

Mitigation: anchor your lookarounds. ^(?=.*X)(?=.*Y).*$ at least limits scope to the whole string, not every position.

When NOT to use lookaround

1. When a character class works. (?=[a-z]) at the start of a pattern is just "matches a lowercase letter." Use [a-z] directly unless you need to NOT consume.

2. When two-pass is clearer. Match broadly with a regex, then filter in code. Often more readable than a single mega-pattern with stacked lookarounds.

3. When the engine doesn't support it. Go, RE2, ripgrep default. Use captures.

The first-pass test

Before reaching for lookaround, ask: "Can I solve this with a capture group + .group(1) or by post-processing?" The capture solution is more portable and often clearer. Reach for lookaround when the value of "don't consume" is high — for splits, for stacked rules, for sed-friendly patterns.

Pippa's lookaround in real code

Search the cwkPippa codebase for (?= and (?<=. You'll find them in route matching, log parsing, and Markdown sanitation — but they're outnumbered ~10:1 by capture groups. The right tool depends on the job.

Code

Lookaround vs alternatives·python
import re

# Lookaround approach — split before capital letters
re.split(r'(?=[A-Z])', 'CamelCaseString')
# ['', 'Camel', 'Case', 'String']

# Alternative: split AND capture, then reassemble
import re
parts = re.findall(r'[A-Z][a-z]*', 'CamelCaseString')
# ['Camel', 'Case', 'String']  — sometimes cleaner

# Lookaround for value extraction
import re
re.findall(r'(?<=user_)\w+', 'user_alice user_bob')
# ['alice', 'bob']

# Capture group equivalent — sometimes preferred for portability
m = re.findall(r'user_(\w+)', 'user_alice user_bob')
# ['alice', 'bob']  — same result, works in Go/RE2 too

# The capture version works in EVERY regex engine.
# The lookbehind version doesn't compile in Go.

External links

Exercise

Take the password validator from lesson 7 and rewrite it WITHOUT lookaround — use a capture-and-test approach in code. Compare the two solutions: which is shorter? More readable? More portable? There's no single right answer — but you should have an opinion now.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.