Python
regex
re.sub
group
programming

Python re.sub group number after number

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

A classic re.sub gotcha appears when a backreference is placed next to digits in the replacement string. Python may interpret \11 as group eleven instead of group one followed by 1, which causes wrong output or group index errors. The safe fix is to use explicit group syntax with \g<1>.

Why the Ambiguity Happens

In replacement strings, backslashes introduce special patterns such as backreferences. When Python sees \1 followed immediately by a digit, it attempts to parse the longest valid group number.

Example ambiguous replacement:

python
1import re
2
3text = "abc123"
4pattern = r"([a-z]+)(\d+)"
5
6# Intended meaning: group 1 then literal digit 1
7print(re.sub(pattern, r"\11", text))

Depending on groups in your pattern, this can be interpreted as group eleven or raise an error.

Use \g<n> for Unambiguous Backreferences

The recommended style is explicit braces-like group syntax.

python
1import re
2
3text = "abc123"
4pattern = r"([a-z]+)(\d+)"
5
6result = re.sub(pattern, r"\g<1>1", text)
7print(result)  # abc1

\g<1> always means group one, and the trailing 1 is treated as literal text.

This syntax also helps with two-digit groups:

python
1import re
2
3text = "AA-BB"
4pattern = r"([A-Z]+)-([A-Z]+)"
5
6print(re.sub(pattern, r"\g<2>-\g<1>", text))  # BB-AA

Prefer Named Groups for Readability

Named groups make replacements easier to maintain.

python
1import re
2
3text = "user:42"
4pattern = r"(?P<name>[a-z]+):(?P<id>\d+)"
5
6result = re.sub(pattern, r"id=\g<id>;name=\g<name>", text)
7print(result)

This is especially useful in long patterns where numeric group indexes are hard to track.

Replacement Functions Avoid Escaping Issues

For complex logic, pass a callable instead of a replacement string.

python
1import re
2
3text = "x=12 y=7"
4pattern = r"([a-z])=(\d+)"
5
6
7def repl(match: re.Match) -> str:
8    key = match.group(1)
9    value = int(match.group(2))
10    return f"{key}={value * 10}"
11
12print(re.sub(pattern, repl, text))

Callable replacements are clear and eliminate many backslash-related mistakes.

Raw Strings and Escape Discipline

Use raw strings for regex patterns and usually for replacement strings as well. Raw strings reduce accidental escape interpretation at Python string level.

python
pattern = r"(\w+)-(\d+)"
replacement = r"\g<1>_\g<2>"

Without raw strings, you must double-escape more sequences, which increases error risk.

Extra Edge Cases to Watch

Group zero means the entire match and can be referenced with \g<0>. This is useful when wrapping matches without rebuilding all groups.

python
import re

print(re.sub(r"cat", r"[\g<0>]", "cat dog cat"))

Also avoid relying on unclear escapes in non-raw replacement strings. Write patterns and replacements consistently so future edits do not silently change behavior.

Add Small Tests for Regex Replacements

Regex replacement bugs are often subtle and data-dependent. Unit tests with representative cases provide fast protection.

python
1import re
2
3
4def normalize(s: str) -> str:
5    return re.sub(r"(\w+)-(\d+)", r"\g<1>_\g<2>", s)
6
7
8def test_normalize() -> None:
9    assert normalize("ab-1") == "ab_1"
10    assert normalize("item-23") == "item_23"

These checks are inexpensive and prevent regressions during refactors.

Performance Considerations

For repeated substitutions, precompile patterns.

python
1import re
2
3compiled = re.compile(r"(\w+)-(\d+)")
4for s in ["ab-1", "cd-2", "ef-3"]:
5    print(compiled.sub(r"\g<1>_\g<2>", s))

Compilation avoids parsing the pattern on every call and keeps code intent explicit.

Common Pitfalls

A common pitfall is using \1 directly before digits and expecting literal text after the group. Another issue is mixing normal and raw strings inconsistently, which can silently change how backslashes are processed. Developers also forget that replacement strings and regex patterns have separate escaping rules. Finally, very long numeric-group patterns become fragile over time, so named groups are usually safer and easier to review.

Summary

  • \1 next to digits can be parsed as a larger group number.
  • Use \g<1> to remove ambiguity in replacement strings.
  • Prefer named groups for maintainable substitutions.
  • Use replacement functions for complex transformation logic.
  • Combine raw strings and precompiled patterns for clearer, safer regex code.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.