本页面有中文版。
Cette page est disponible en français.
Esta página está disponible en español.
Guides1 live hunt inside

Regex anchors: ^, $ and multiline mode explained

^ and $ don't match characters, they match positions. Why a pattern without anchors checks less than you think, what the m flag changes, how Python's $ treats a trailing newline, and when to use \A and \z instead.

Regex Hunter logo

· 6 min read

/^\d{5}$/

match

  • 90210
  • 10001
  • 02139
  • 60614

spare

  • 9021
  • 902100
  • ZIP 90210
  • 90210-1234

Most regex tokens match a character. Anchors match a position: the start of the text, the end, the gap between two characters. They take up no width at all.

They're also the most common thing missing from a validation pattern. This post covers ^ and $, what the m flag changes, and the corner cases that differ between languages. Every example is live, so edit the patterns and watch the lines change.

01A pattern without anchors checks less than you think

Regex engines search: they report a match if the pattern fits anywhere in the text. So a "five digit ZIP code" pattern without anchors is really "contains five digits in a row":

Matches4/4 as expected
/\d{5}/
  • 90210
  • 902100
  • ZIP 90210
  • 90210-1234
Try it in the Playground
Every line matches. Each one contains five digits somewhere.

02^ and $

^ matches at the start of the text and $ at the end. Put them around the pattern and the whole input has to fit:

Matches6/6 as expected
/^\d{5}$/

Should match2/2

  • 90210
  • 02139

Should not4/4

  • 9021
  • 902100
  • ZIP 90210
  • 90210-1234
Try it in the Playground
Now there's no room for anything before or after the five digits.

You can use just one of them. ^# means "starts with #", and \.pdf$ means "ends with .pdf":

Matches4/4 as expected
/\.pdf$/

Should match2/2

  • report.pdf
  • 2026 taxes.pdf

Should not2/2

  • report.pdf.exe
  • pdf-guide.txt
Try it in the Playground
Only the end of the text is pinned. The rest can be anything.

03Anchors around alternation

| has the lowest priority of anything in a pattern, lower than the anchors. So ^cat|dog$ doesn't mean "exactly cat or dog". It means "starts with cat, or ends with dog":

Matches4/4 as expected
/^cat|dog$/
  • cat
  • dog
  • catalog
  • hotdog
Try it in the Playground
The last two lines match too. ^ only applies to cat, and $ only to dog.

Wrap the choices in a group so both anchors apply to both:

Matches4/4 as expected
/^(cat|dog)$/

Should match2/2

  • cat
  • dog

Should not2/2

  • catalog
  • hotdog
Try it in the Playground
The group makes the anchors apply to every alternative.

This one bites in real code, in allow-lists like ^admin|editor$ that quietly accept admin-old or not-an-editor.

04Multiline mode

By default, ^ and $ match only at the very start and end of the whole text, even if it contains line breaks. The m flag changes them to match at the start and end of every line:

const text = "apple\nbanana\ncherry";

text.match(/^\w+$/g);   // null: "apple\nbanana\ncherry" isn't one word
text.match(/^\w+$/gm);  // ["apple", "banana", "cherry"]

That's what you want when you're pulling things out of a file line by line. In Python it's re.MULTILINE, or (?m) at the start of the pattern:

import re

re.findall(r"^#+ .*$", markdown, re.MULTILINE)  # every heading line

The flag only changes ^ and $. A dot still won't cross a line break. That's a different flag, s, covered in the post on regex flags.

05The trailing newline trap

Here the languages split. In JavaScript, without m, $ matches only at the very end. In Python, $ also matches just before a newline at the very end:

import re

re.match(r"^\d{5}$", "90210\n")    # matches in Python
re.fullmatch(r"\d{5}", "90210\n")  # None
/^\d{5}$/.test("90210\n");  // false in JavaScript

So in Python, ^\d{5}$ accepts a ZIP code with a newline stuck on the end. That can matter if the value goes into a database or a file name. Two fixes:

  • Use re.fullmatch(), which must consume the whole string.
  • Use \A and \Z: in Python, \A is the very start and \Z the very end, with no newline exception. (Ruby, PCRE and Java spell the strict end \z.)

JavaScript has no \A or \z, but without the m flag its ^ and $ already behave like them.

06Word boundaries are anchors too

\b is another zero-width anchor: the position between a word character and a non-word character. It's how you match a whole word inside text:

Matches5/5 as expected
/\bcat\b/

Should match3/3

  • the cat sat
  • cat
  • a cat.

Should not2/2

  • concatenate
  • cats
Try it in the Playground
cat on its own, but not inside another word.

07Try one

Catch the lines that are a hashtag and nothing else:

Live huntpar 6

Catch hashtags: a # followed by letters, digits or underscores, and nothing else.

Targets · 0/5 caught

  • #regextarget
  • #100DaysOfCodetarget
  • #atarget
  • #snake_casetarget
  • #2026target

Innocents · 0 hit

  • regexinnocent
  • ##doubleinnocent
  • #two wordsinnocent
  • #innocent
  • #dash-edinnocent
  • a#taginnocent

Type a regex — the board updates as you go.

Try it in the Playground
Like this? A new one drops every day.Play today’s hunt

08Checklist

  • Without anchors, a pattern only checks that the text contains a match.
  • ^ is the start and $ is the end. Use both for validation.
  • Group alternatives inside anchors: ^(cat|dog)$, not ^cat|dog$.
  • The m flag makes ^ and $ work per line.
  • In Python, $ allows one trailing newline. Use re.fullmatch() or \Z.
  • \b anchors a whole word.

09Keep practising