Table of Contents

    Lookbehind

    PYTHON & REGULAR EXPRESSIONS

    Regex Lookbehind — The Complete Guide

    Master positive and negative lookbehind assertions to match text based on what precedes it — without consuming those characters.

    Introduction

    A lookbehind is a regex assertion that checks whether the text at the current position is (or is not) preceded by a given pattern — without including that preceding pattern in the match. It's the mirror image of a lookahead: instead of peeking forward, it glances backward. This "zero-width" behaviour lets you extract or validate text based on the context before it, such as grabbing a number only when it follows a currency symbol.

    In one line: A lookbehind checks what comes before the current position to decide a match — but never consumes those characters.

    Real-World Analogy

    The Rear-View Mirror

    A driver glances in the rear-view mirror to check what's behind before changing lanes — but that glance doesn't move the car backward. A lookbehind is that mirror: it verifies what precedes the match to allow it, without stepping back over any characters.

    Prerequisites

    Before You Start

    • Python 3.x installed
    • Understanding of the re module (search, findall, sub)
    • Familiarity with groups, tokens (\d, \w), and escaping special characters
    • Comfort using raw strings r"..." for patterns
    • Helpful: prior knowledge of lookahead assertions

    The Key Idea: Zero-Width

    Like all lookarounds, a lookbehind is zero-width. It tests a condition about the characters before the current position but does not consume them or include them in the result — the matched text starts right after the lookbehind succeeds.

    CORE CONCEPT
    Lookbehind checks the past but keeps the present

    The Two Lookbehind Assertions

    1

    Positive Lookbehind (?<=...)

    Match only if preceded by a pattern.

    (?<=Y)X matches X only when it is immediately preceded by Y. Y is not part of the match.

    2

    Negative Lookbehind (?<!...)

    Match only if NOT preceded by a pattern.

    (?<!Y)X matches X only when it is not immediately preceded by Y.

    Syntax Reference

    Syntax Name Matches X when...
    (?<=Y)X Positive lookbehind X is preceded by Y
    (?<!Y)X Negative lookbehind X is not preceded by Y

    Positive Lookbehind (?<=...)

    Extract a number only when it follows a dollar sign — without capturing the sign.

    import re
    
    text = "$100 and €200 and $300"
    
    # \d+ that is preceded by "$" — but "$" is NOT part of the match
    dollars = re.findall(r"(?<=\$)\d+", text)
    print(dollars)   # ['100', '300']
    Notice The result contains only the numbers — the $ was checked but excluded from the match.

    Negative Lookbehind (?<!...)

    Extract numbers that are NOT preceded by a dollar sign.

    import re
    
    text = "$100 and €200 and $300"
    
    # numbers NOT preceded by "$"
    non_dollars = re.findall(r"(?<!\$)\b\d+", text)
    print(non_dollars)   # ['200']

    Python's Fixed-Width Limitation

    The biggest gotcha: in Python's built-in re module, a lookbehind must be fixed-width — every alternative must have the same, known length. Variable-length quantifiers (+, *, {2,4}) are not allowed.

    import re
    
    # OK — fixed width (exactly 3 characters)
    re.findall(r"(?<=abc)\d+", "abc123")     # works
    
    # ERROR — variable width
    # re.findall(r"(?<=a+)\d+", "aaa123")
    # raises: re.error: look-behind requires fixed-width pattern
    Not allowed (?<=\d+), (?<=ab?c), and (?<=colou?r) — all variable-width, all raise errors in re.
    Workaround Install the third-party regex module (pip install regex) which fully supports variable-width lookbehind.
    import regex   # third-party module
    
    # Variable-width lookbehind works here
    print(regex.findall(r"(?<=a+)\d+", "aaa123"))   # ['123']

    Alternation Must Be Equal Width

    Alternatives inside a lookbehind are allowed only if they are all the same length.

    import re
    
    # OK — both alternatives are 3 characters
    print(re.findall(r"(?<=Mr\.|Ms\.)\s?\w+", "Mr. Smith"))  # note: Mr. and Ms. both length 3+dot
    
    # Both "cat" and "dog" are 3 chars -> allowed
    print(re.findall(r"(?<=cat|dog)\d+", "cat42 dog99"))     # ['42', '99']

    Using Lookbehind in Replacements

    Mask digits that appear after a label, keeping the label intact.

    import re
    
    text = "PIN:1234 CODE:5678"
    
    # Replace digits that follow "PIN:" with asterisks
    masked = re.sub(r"(?<=PIN:)\d", "*", text)
    print(masked)   # PIN:**** CODE:5678

    Lookbehind vs Lookahead

    Aspect Lookbehind Lookahead
    Direction Checks text before Checks text after
    Positive syntax (?<=...) (?=...)
    Negative syntax (?<!...) (?!...)
    Position in pattern Before the main token After the main token
    Width in Python re Must be fixed-width Can be variable-width

    Zero-Width, Backward

    A lookbehind at position \(p\) inspects the characters in the interval ending exactly at \(p\). For a fixed-width lookbehind of length \(w\), it examines the slice \([\,p-w,\; p\,)\) but leaves the match position unchanged:

    \[ \text{inspect } [\,p-w,\; p\,) \quad\Rightarrow\quad p_{\text{after}} = p_{\text{before}} \]

    Because \(w\) must be a single constant in Python's re, the engine always knows exactly how far back to look.

    Best Practices

    Do This

    • Keep Python lookbehind fixed-width; switch to the regex module for variable-width
    • Escape special characters like $, ., and ( inside the lookbehind
    • Use lookbehind to exclude a prefix (like $) from the result
    • Combine with \b to avoid partial matches
    • Prefer lookbehind in re.sub to transform text after a label without touching the label
    • Ensure all alternation branches have equal length

    Common Mistakes

    Bad Using a variable-width lookbehind like (?<=\w+) in re — it raises a "fixed-width pattern" error.
    Good Use a fixed-length lookbehind, or the regex module for variable width.
    Bad Forgetting to escape $: (?<=$) treats $ as end-of-string, not a literal dollar sign.
    Good Escape it: (?<=\$) to match a literal dollar sign before your target.

    Interview Questions

    Question Short Answer
    What is a lookbehind? A zero-width assertion that checks whether the current position is preceded by a pattern.
    Positive vs negative lookbehind? (?<=...) requires the preceding pattern; (?<!...) forbids it.
    Key limitation in Python's re? Lookbehind must be fixed-width — no variable quantifiers.
    How to get variable-width lookbehind? Use the third-party regex module instead of re.
    How does it differ from lookahead? Lookbehind checks text before; lookahead checks text after — both zero-width.

    Quick Revision

    Assertion Syntax Meaning
    Positive lookbehind (?<=...) Preceded by
    Negative lookbehind (?<!...) Not preceded by
    Width rule (re) Fixed-width only No + * {n,m}
    Variable width regex module Allowed there

    Key Takeaways

    A lookbehind is a zero-width assertion that matches text by what precedes it — using (?<=...) for "preceded by" and (?<!...) for "not preceded by". Remember Python's re requires fixed-width lookbehind; reach for the regex module when you need variable width.