Swift Regular Expressions: Regex Literals and RegexBuilder

Swift includes modern, type-safe support for regular expressions, so you can search text, validate input, extract values, and transform strings without relying only on older Foundation APIs. In this article, you will learn what Swift regular expressions are, how regex literals and RegexBuilder work, when to use each approach, and how to avoid the mistakes that commonly confuse beginners.

Quick answer: In Swift 5+, you can write regular expressions either as regex literals such as /\d+/ or by building them with RegexBuilder. Use literals when the pattern is short and familiar, and use RegexBuilder when you want clearer, more structured, and more type-safe patterns.

Difficulty: Intermediate

Helpful to know first: You will understand this better if you already know basic Swift syntax, how String values work, and how functions and conditionals are used in simple programs.

1. What Is Swift Regular Expressions?

A regular expression, usually called a regex, is a pattern used to find, validate, or extract text. Swift’s modern regex system lets you describe text patterns directly in Swift code and work with matches in a safer way than many older regex APIs.

For example, a simple regex can find one or more digits inside a string. More advanced patterns can capture multiple parts, such as a username and domain from an email-like value.

Swift regex support is powerful, but it still helps to think carefully about what text you want to match. A regex that is too broad can accept invalid input, while one that is too strict can reject valid data.

2. Why Swift Regular Expressions Matters

Text processing appears in many real apps. User input, log files, imported CSV data, API responses, filenames, and command-line arguments often contain structured text that needs to be checked or parsed.

You would use Swift regular expressions when:

You would not automatically use regex for every string task. If a simple string method expresses the intent more clearly, that is often the better choice. For example, checking whether a file ends with .jpg is usually clearer with hasSuffix than with a regex.

3. Basic Syntax or Core Idea

Swift gives you two main ways to create a regex: a regex literal and RegexBuilder.

Using a regex literal

A regex literal is written between slashes. This example looks for one or more digits.

let text = "Order 4829 shipped"
let regex = /\d+/

if let match = try? text.firstMatch(of: regex) {
    print(match.0)
}

Here is what each part means:

This prints the first number found in the string.

Using RegexBuilder

RegexBuilder lets you describe the same pattern using readable Swift components.

import RegexBuilder

let text = "Order 4829 shipped"

let regex = Regex {
    OneOrMore {
        CharacterClass.digit
    }
}

if let match = try? text.firstMatch(of: regex) {
    print(match.0)
}

This version matches the same digits, but it is often easier to read when patterns become longer or more structured.

Whole match vs first match

A common point of confusion is the difference between matching part of a string and matching the entire string.

let code = "AB-123"
let regex = /[A-Z]{2}-\d{3}/

let partial = try? code.firstMatch(of: regex)
let whole = try? code.wholeMatch(of: regex)

firstMatch(of:) looks for a matching part somewhere in the string. wholeMatch(of:) requires the entire string to fit the pattern.

4. Step-by-Step Examples

Example 1: Find the first number in a sentence

This is one of the simplest practical uses of regex. The pattern below finds a sequence of digits.

let message = "Your table number is 27."
let numberRegex = /\d+/

if let match = try? message.firstMatch(of: numberRegex) {
    print("Found number:", match.0)
}

This prints the first digit sequence found in the sentence. It is useful for basic extraction tasks from mixed text.

Example 2: Validate a simple product code

When you want the entire input to match a pattern, use wholeMatch(of:). This example expects exactly two uppercase letters, a dash, and three digits.

let productCode = "TV-204"
let productRegex = /[A-Z]{2}-\d{3}/

if (try? productCode.wholeMatch(of: productRegex)) != nil {
    print("Valid product code")
} else {
    print("Invalid product code")
}

This is better than a partial search because the whole input must match the format, not just one part of it.

Example 3: Capture parts of a date

Regex becomes especially useful when you want to extract multiple values from one string.

let dateText = "2025-08-17"
let dateRegex = /(\d{4})-(\d{2})-(\d{2})/

if let match = try? dateText.wholeMatch(of: dateRegex) {
    let year = match.1
    let month = match.2
    let day = match.3

    print("Year:", year)
    print("Month:", month)
    print("Day:", day)
}

The full match is stored at match.0, and captured groups begin at match.1. This is a common way to pull structured values from formatted text.

Example 4: Build the same date pattern with RegexBuilder

For complex patterns, RegexBuilder can be easier to read and maintain.

import RegexBuilder

let dateText = "2025-08-17"

let dateRegex = Regex {
    Capture {
        Repeat(4) {
            CharacterClass.digit
        }
    }
    "-"
    Capture {
        Repeat(2) {
            CharacterClass.digit
        }
    }
    "-"
    Capture {
        Repeat(2) {
            CharacterClass.digit
        }
    }
}

if let match = try? dateText.wholeMatch(of: dateRegex) {
    print(match.1, match.2, match.3)
}

This example shows the same idea as the literal version, but with a builder structure that many developers find easier to edit later.

5. Practical Use Cases

6. Common Mistakes

Mistake 1: Using firstMatch when the entire string must be valid

Beginners often search for a match anywhere in the string when they actually want to validate the whole input.

Problem: This code accepts invalid input because it only checks whether some part of the string matches the pattern.

let input = "Code: AB-123 extra"
let regex = /[A-Z]{2}-\d{3}/

if (try? input.firstMatch(of: regex)) != nil {
    print("Accepted")
}

Fix: Use wholeMatch(of:) when the entire input must follow the expected format.

let input = "AB-123"
let regex = /[A-Z]{2}-\d{3}/

if (try? input.wholeMatch(of: regex)) != nil {
    print("Accepted")
}

The corrected version works because the whole string must match the regex, not just one substring.

Mistake 2: Forgetting that regex is not always the clearest tool

Some tasks are so simple that a regular expression makes the code harder to read than necessary.

Problem: This code uses a regex for a basic suffix check, which adds complexity without any real benefit.

let filename = "photo.jpg"
let regex = /.*\.jpg/

if (try? filename.wholeMatch(of: regex)) != nil {
    print("JPEG file")
}

Fix: Use direct string APIs when they express the intent more clearly.

let filename = "photo.jpg"

if filename.hasSuffix(".jpg") {
    print("JPEG file")
}

The corrected version works because hasSuffix is simpler, clearer, and designed for this exact check.

Mistake 3: Assuming a regex match always exists

Match methods return an optional result because the pattern may not be found.

Problem: This code force-unwraps a match that may be nil, which can crash at runtime if the text does not match.

let text = "No number here"
let regex = /\d+/

let match = try! text.firstMatch(of: regex)!
print(match.0)

Fix: Safely unwrap the optional result before using the match.

let text = "No number here"
let regex = /\d+/

if let match = try? text.firstMatch(of: regex) {
    print(match.0)
} else {
    print("No match found")
}

The corrected version works because it handles the case where the regex does not match the text.

Mistake 4: Choosing an unreadable literal when RegexBuilder would be clearer

Regex literals are concise, but complex patterns can become difficult to understand and maintain.

Problem: This pattern is compact, but it is harder to review and update because the structure is hidden inside punctuation-heavy syntax.

let regex = /(\d{4})-(\d{2})-(\d{2})/

Fix: Use RegexBuilder when readability and maintenance matter more than compact syntax.

import RegexBuilder

let regex = Regex {
    Capture {
        Repeat(4) { CharacterClass.digit }
    }
    "-"
    Capture {
        Repeat(2) { CharacterClass.digit }
    }
    "-"
    Capture {
        Repeat(2) { CharacterClass.digit }
    }
}

The corrected version works because the pattern structure is easier to see and modify.

7. Best Practices

Practice 1: Start with the simplest string tool that solves the problem

Regex is powerful, but clarity matters. Prefer plain string APIs for straightforward tasks, and switch to regex when the pattern truly needs it.

// Less preferred for a simple prefix check
let regex = /ERROR:.*/

// Preferred
let line = "ERROR: Disk full"
if line.hasPrefix("ERROR:") {
    print("Error line")
}

This keeps simple code readable and reserves regex for cases where pattern matching is genuinely useful.

Practice 2: Use wholeMatch for validation rules

When checking a required format, make your intent explicit by requiring the entire string to match.

let zipCode = "12345"
let zipRegex = /\d{5}/

if (try? zipCode.wholeMatch(of: zipRegex)) != nil {
    print("Looks valid")
}

This practice reduces accidental false positives from partial matches.

Practice 3: Prefer RegexBuilder for long or business-critical patterns

If a regex is likely to be edited later, builder syntax can make maintenance safer and easier.

import RegexBuilder

let idRegex = Regex {
    "ID-"
    Repeat(6) {
        CharacterClass.digit
    }
}

This version is easier to inspect than a compact literal when requirements change later.

8. Limitations and Edge Cases

9. Swift Regex Literals vs RegexBuilder

These two approaches solve the same broad problem, but they are best in slightly different situations.

ApproachBest forStrengthTradeoff
Regex literalShort, familiar patternsCompact and quick to writeCan become hard to read
RegexBuilderLonger or structured patternsReadable, composable, type-safe styleMore verbose

Use a regex literal when the pattern is short and the meaning is obvious, such as finding digits or checking a small code format. Use RegexBuilder when the pattern contains several parts, captures, or repeated rules that would be difficult to review in slash syntax.

Compared with NSRegularExpression, Swift’s modern regex support usually feels more natural in Swift code. The newer API integrates better with matches and captures, while older Foundation-based regex often requires more manual range handling.

10. Practical Mini Project

Let’s build a small validator and parser for order references. The required format is two uppercase letters, a dash, and four digits, such as AB-4829.

import RegexBuilder

let orderRegex = Regex {
    Capture {
        Repeat(2) {
            CharacterClass("A"..."Z")
        }
    }
    "-"
    Capture {
        Repeat(4) {
            CharacterClass.digit
        }
    }
}

func parseOrderCode(_ text: String) {
    if let match = try? text.wholeMatch(of: orderRegex) {
        let prefix = match.1
        let number = match.2

        print("Valid order code")
        print("Prefix:", prefix)
        print("Number:", number)
    } else {
        print("Invalid order code:", text)
    }
}

parseOrderCode("AB-4829")
parseOrderCode("A1-4829")
parseOrderCode("AB-48")

This mini project validates the full input and extracts the two meaningful parts. It shows a realistic pattern: check a format, then use captured values in program logic.

11. Key Points

12. Practice Exercise

Build a program that checks whether a username follows this rule:

Expected output: For a valid username, print Valid username. Otherwise, print Invalid username.

Hint: Use wholeMatch(of:) so the entire input must follow the rule.

let username = "alex42"
let usernameRegex = /[a-z][a-z0-9]{3,11}/

if (try? username.wholeMatch(of: usernameRegex)) != nil {
    print("Valid username")
} else {
    print("Invalid username")
}

13. Final Summary

Swift regular expressions give you a modern way to work with text patterns directly in Swift. You can use regex literals for concise matching and RegexBuilder when you want a more readable and maintainable structure. Together, these tools make it easier to validate input, search strings, and extract useful data from text.

The most important habit is choosing the right level of complexity. Use regular expressions when the problem truly involves a pattern, but prefer simple string APIs when they are clearer. As a next step, practice writing small validation rules and capture-based parsers so you become comfortable deciding between regex literals, RegexBuilder, and ordinary string methods.