StackKit

Regular Expressions for Developers: JavaScript and Python

By · · 4 min read

A regular expression is a compact pattern for finding and extracting text. They are worth learning because the same small core works in nearly every language, editor and command-line tool. This guide focuses on the parts you will use most, with examples that were run in both Node.js and Python 3.

The running example is a log line:

2026-11-02 10:15:42 ERROR payment failed order=1042 user=ada@example.com

The building blocks

Pattern Matches
. any character except a newline
\d, \w, \s a digit, a word character (letters, digits, _), whitespace
[abc], [^abc] one of a, b, c; anything except a, b, c
*, +, ? zero or more, one or more, zero or one
{3}, {2,4} exactly 3; between 2 and 4
^, $ start and end (of the string, or of each line with the multiline flag)
( ) a group, which also captures the matched text
a|b a or b

Capturing parts of a match

Parentheses capture. Named groups make the pattern self-documenting. JavaScript writes them (?<name>...) and Python writes (?P<name>...).

const log = "2026-11-02 10:15:42 ERROR payment failed order=1042 user=ada@example.com";
const m = log.match(
  /^(?<date>\d{4}-\d{2}-\d{2}) (?<time>[\d:]+) (?<level>[A-Z]+) (?<msg>.*)$/
);
console.log(m.groups.level, "|", m.groups.msg);
// ERROR | payment failed order=1042 user=ada@example.com
import re

m = re.match(
    r"(?P<date>\d{4}-\d{2}-\d{2}) (?P<time>[\d:]+) (?P<level>[A-Z]+) (?P<msg>.*)",
    log,
)
print(m.group("level"), "|", m["msg"])

Finding every match

In JavaScript, use matchAll with the global flag. In Python, use findall (or finditer for match objects).

console.log([..."a1b22c333".matchAll(/\d+/g)].map((x) => x[0]));
// [ '1', '22', '333' ]
print(re.findall(r"\d+", "a1b22c333"))   # ['1', '22', '333']

Without the g flag in JavaScript, match returns only the first result.

Replacing with captured groups

Reference captures in the replacement text: $1 in JavaScript, \1 in Python.

console.log("2026-11-02".replace(/(\d+)-(\d+)-(\d+)/, "$3/$2/$1"));
// 02/11/2026
print(re.sub(r"(\d+)-(\d+)-(\d+)", r"\3/\2/\1", "2026-11-02"))
# 02/11/2026

Greedy versus lazy

Quantifiers are greedy by default: they match as much as possible. Adding ? makes them lazy (as little as possible). This is the cause of many "my regex matched too much" bugs.

const html = "<b>one</b><b>two</b>";
console.log(html.match(/<b>.*<\/b>/)[0]);   // <b>one</b><b>two</b>
console.log(html.match(/<b>.*?<\/b>/)[0]);  // <b>one</b>

(Regexes are fine for quick extractions, but do not use them to parse real HTML; use an HTML parser.)

Lookbehind and lookahead

Lookarounds match a position without consuming text. To extract prices after a dollar sign without including it:

console.log("price: $5, $12.50".match(/(?<=\$)\d+(\.\d+)?/g));
// [ '5', '12.50' ]
print(re.findall(r"(?<=\$)\d+(?:\.\d+)?", "price: $5, $12.50"))
# ['5', '12.50']

(?=...) is a positive lookahead, and (?!...) and (?<!...) are the negative forms.

Differences between JavaScript and Python to remember

  • Named groups: (?<n>) in JavaScript, (?P<n>) in Python.
  • Whole-string matching: re.match anchors only at the start; use re.fullmatch to require the whole string. In JavaScript, add ^ and $ yourself.
  • Dot and newlines: . does not match a newline in either language. Use the s flag in JavaScript (/a.c/s) or re.DOTALL in Python.
  • Compile once: in Python, re.compile(...) is worthwhile when reusing a pattern in a loop, though the module also caches recently used patterns.
print(re.match(r"\d+", "abc123"))            # None  (match anchors at the start)
print(re.search(r"\d+", "abc123").group())   # 123

Escaping

Characters with special meaning need a backslash to be matched literally: . * + ? ( ) [ ] { } ^ $ | \. A frequent trap is "foo.bar".split(/./), which splits on every character; use /\./, which gives [ 'foo', 'bar' ]. In Python, prefer raw strings (r"...") so you do not need to double every backslash.

Do not validate everything with regex

A short pattern such as ^[\w.+-]+@[\w-]+(\.[\w-]+)+$ is fine as a first-pass check that something looks like an email address (ada@example.com passes, ada@ fails). It is not a full implementation of the email standard. The reliable test for an address is sending a message to it.

Avoid catastrophic backtracking

Nested quantifiers such as (a+)+$ can take exponential time on certain inputs. If you run regexes on untrusted input, keep patterns simple, anchor them, and limit the input length.

Tools for learning

Use an interactive tester such as regex101.com (choose the right flavour) to see what each part matches, and write a few test strings, including ones that should not match.

Summary

Learn the building blocks, use named groups for readability, reach for lazy quantifiers when a match is too greedy, and remember the small differences between JavaScript and Python. With those, most everyday text-wrangling tasks fit in one line.