Concepts / Lazy Patterns

Lazy Patterns

The default notation is ECMAScript. The notation can be adjusted to match other standards, so the first question when reading an unfamiliar pattern is which notation's rules apply.

  • Programming

Why Regular Expressions Feel Write-Only

Many learners meet regular expressions and conclude that they are a kind of code that only its author can ever read. A single line can hold a dense cluster of symbols, and once it is written, it seems to resist any later review. The source for this topic names this belief directly: regular expressions can easily become a write-only language. The useful question is not whether that danger exists, but what habits keep a pattern readable and what the notation itself gives you to work with.

Before reading further, pause and ask yourself: what would make a pattern easy for a future reader to understand, and what would make it impossible? Keep your answer in mind. The rest of this article tests that intuition against what the source gives.

The Default Notation and Its Adjustment

Definition

A regular expression notation is a set of rules about which symbols mean what. Every regular expression is written in some notation, and the rules of that notation determine how you read it.

The default regular expression notation described in this topic is that of ECMAScript. Unless you choose otherwise, the rules you are reading are the ECMAScript rules.

The notation is not fixed to that one default. The source states that it can be adjusted to match various standards. The same general idea of a pattern can therefore be expressed under different notational conventions, and you select the convention that your situation requires. What changes between conventions is the set of rules for reading the symbols, not the underlying idea of describing text by pattern.

Key point

The default notation is ECMAScript. The notation can be adjusted to match other standards, so the first question when reading an unfamiliar pattern is which notation's rules apply.

Why Dense Patterns Resist Reading

The belief that regular expressions are unreadable is partly true, because a regular expression packs a lot of meaning into very few characters. A reader has to recognize each symbol, understand how symbols combine, and keep track of which part of the pattern each character belongs to. When a pattern grows without discipline, these combinations become hard to follow, even for the person who wrote them.

The practical correction is restraint. Do not write the most compact pattern that happens to work. Write the pattern that a later reader can verify at a glance. The source gives this as a tip for using regular expressions effectively, and it applies to every pattern you write, whether short or long.

  • Common mistake

    Choosing the shortest pattern because it works

    Why it fails

    A pattern that works but cannot be checked at a glance will be hard for a later reader to verify, and that defeats the purpose of writing it down.

    Do this instead

    Write the pattern that a later reader can verify at a glance, even if it is a little longer.

  • Common mistake

    Assuming the notation is fixed

    Why it fails

    The notation can be adjusted to match various standards, so the same symbols may not carry the meaning you expect.

    Do this instead

    Identify which notation is in use before reading or writing a pattern.

Reusing a Subpattern with \i

The notation includes \i, which the source describes as allowing you to express a subpattern in terms of a previous subpattern. Instead of spelling out the same subpattern again, you refer back to an earlier one. The result reads as a composition of named ideas rather than one long sequence of symbols.

The Question Mark That Makes a Pattern Lazy

The source gives one specific modifier: the question mark ?. Placing ? in a pattern makes that pattern lazy. A lazy pattern matches as little text as it can while still satisfying the pattern, as opposed to matching as much as possible. Because the modifier is a single symbol, the change it makes is easy to overlook when you read a pattern.

What do you think happens?

If a pattern would otherwise match a long stretch of text, would the lazy version match a longer or a shorter stretch?

  • A longer stretch

  • A shorter stretch

Reveal answer
Answer:

A shorter stretch

A lazy pattern matches as little text as it can while still satisfying the pattern, as opposed to matching as much as possible.

To see the difference concretely, consider a generated illustration. Suppose the input text is the string <b>one</b> and <b>two</b>, and the pattern is written as <b>.*</b> with the star matching as much as possible, compared with <b>.*?</b> where the question mark makes the pattern lazy. The diagram below shows where each version ends its match on the same input. This is an original illustration of the lazy-versus-greedy idea the source describes, not an example taken from the source.

Greedy matchmatches as much as possible<b>one</b> and<b>two</b>one match ending after two<b>one</b>first match ends at firstclosing tagLazy matchmatches as little aspossible<b>two</b>second match ends at secondclosing tagproducesproducesproduces
Given the same input, how does the matched text differ when the quantifier is greedy versus lazy, and where does each match end?

In the greedy version, one match covers everything from the first opening tag through the last closing tag. In the lazy version, the match stops at the first place the pattern can be satisfied, so the same input yields two separate matches. The question mark did not change the characters being searched for; it changed how much text each match is allowed to take.

Iterating Over a Stream with Regular Expressions

Regular expressions are not only used to test a single string. The source explains that you can use regex iterators to iterate over a stream while looking for a pattern. The pattern is applied repeatedly as you move through a stream of text, and each match the iterator finds is a point where your program can act.

Start of streamSearch for patternMatch foundact on this matchResume after matchcontinue from the end ofthe matchNo more matchesbeginpattern locatednext positionkeep searchingstream exhausted
After one match is found, where does the iterator resume searching, and how does it advance through the input until no matches remain?
Worked example

Tracing an iterator over a short stream

A generated stream of text reads: cat, dog, cat. The pattern is cat. Trace how an iterator moves through the stream.

  1. 1
    Start at the beginning

    The iterator begins at the start of the stream and searches for the pattern cat.

  2. 2
    First match

    The first cat is found. Your program can act on this match.

  3. 3
    Resume after the match

    The search continues from where the first match ended, not from the start of the stream again.

  4. 4
    Second match

    The search finds the second cat later in the stream. Again, the program can act on it.

  5. 5
    Exhaust the stream

    No further matches remain, so the iteration ends.

Result

The iterator yields two matches, one for each occurrence of cat, and then stops. This trace is an original illustration of the process the source describes; the source names the iterator and its purpose but does not spell out every step.

Practicing the Lazy Habit

MEDIUM

Using the generated input <b>one</b> and <b>two</b>, state where the greedy pattern <b>.*</b> ends its match and where the lazy pattern <b>.*?</b> ends its first match. Then explain in one sentence why the lazy version produces more than one match when the iterator runs over the whole input.

Hints
  • Recall that a lazy pattern matches as little text as it can while still satisfying the pattern.
  • Think about where the first closing tag appears in the input.
  • Remember that the iterator resumes searching after each match ends.

Mistakes When Reading Lazy Patterns

  • Common mistake

    Overlooking the question mark

    Why it fails

    The ? modifier is a single symbol, so it is easy to miss, yet it changes how much text the pattern matches.

    Do this instead

    Check every quantifier for a trailing ? before deciding what a pattern will match.

  • Common mistake

    Assuming a lazy pattern searches for different text

    Why it fails

    The ? changes how much text is matched, not which characters the pattern looks for.

    Do this instead

    Keep the matched characters in mind separately from the length of the match.

  • Common mistake

    Treating an iterator as a single test

    Why it fails

    An iterator applies the pattern repeatedly across a stream, so one pattern can produce several matches.

    Do this instead

    Expect multiple match points and decide how your program should act on each one.

Key Takeaways

  • The default regular expression notation is ECMAScript, and the notation can be adjusted to match various standards.
  • Regular expressions can become a write-only language, so restraint is the practical correction: write the pattern a later reader can verify at a glance.
  • The ? modifier makes a pattern lazy, so it matches as little text as it can while still satisfying the pattern, as opposed to matching as much as possible.
  • A regex iterator moves through a stream of text, applying the pattern repeatedly and giving your program a point to act on at each match.
  • The \i notation lets one subpattern be expressed in terms of a previous subpattern, so a pattern can read as a composition of named parts.

Related concepts

More concepts starting with L