Question

Why \d Can Be Slower Than [0-9] in C# Regex

csharpregexperformance

Question

In C#, I tested three regular expression patterns for detecting whether a string contains a digit:

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace SO_RegexPerformance
{
    class Program
    {
        static void Main(string[] args)
        {
            var rand = new Random(1234);
            var strings = new List<string>();

            // Generate 10,000 random strings
            for (var i = 0; i < 10000; i++)
            {
                var sb = new StringBuilder();

                for (var c = 0; c < 1000; c++)
                {
                    sb.Append((char)('a' + rand.Next(26)));
                }

                // Add a digit to about half of the strings
                if (rand.Next(2) == 0)
                {
                    sb[rand.Next(sb.Length)] = (char)('0' + rand.Next(10));
                }

                strings.Add(sb.ToString());
            }

            var baseTime = TestPerformance(strings, @"\d");
            Console.WriteLine();

            var testTime = TestPerformance(strings, "[0-9]");
            Console.WriteLine("  {0:P2} of first", testTime.TotalMilliseconds / baseTime.TotalMilliseconds);

            testTime = TestPerformance(strings, "[0123456789]");
            Console.WriteLine("  {0:P2} of first", testTime.TotalMilliseconds / baseTime.TotalMilliseconds);
        }

        private static TimeSpan TestPerformance(List<string> strings, string regex)
        {
            var sw = new Stopwatch();
            int successes = 0;
            var rex = new Regex(regex);

            sw.Start();
            foreach (var str in strings)
            {
                if (rex.Match(str).Success)
                {
                    successes++;
                }
            }
            sw.Stop();

            Console.Write("Regex {0,-12} took {1} result: {2}/{3}", regex, sw.Elapsed, successes, strings.Count);
            return sw.Elapsed;
        }
    }
}

The results showed that \d was slower than both [0-9] and [0123456789], while the latter two performed similarly.

Why does this happen?

  • I expected [0-9] to be more efficient than [0123456789].
  • I also expected \d to behave like shorthand for [0-9], but it appears to do more work.

What is the difference between \d and [0-9] in the C# regex engine, and why might \d be slower?

Short Answer

By the end of this page, you will understand why \d and [0-9] are not always equivalent in C# regular expressions, how Unicode character classes affect performance, and why benchmark results can differ even when patterns look similar. You will also learn when to prefer readability, correctness, or raw speed.

Concept

In C# regular expressions, \d is not always the same thing as [0-9].

  • [0-9] means: match one ASCII character in the range from '0' to '9'.
  • [0123456789] means exactly the same set of ASCII characters, just written out explicitly.
  • \d means: match a digit character class.

In .NET regex, \d is Unicode-aware by default. That means it can match more than just ASCII digits. It may also match other characters considered digits in Unicode, depending on the regex engine rules in use.

Because of that, \d can require a more general classification check than [0-9], which only needs a simple range comparison.

Why this matters

When you write regex, you are choosing between:

  • meaning: what characters should match
  • portability: whether behavior is consistent across systems
  • performance: how much work the engine must do

If you only want ASCII digits, [0-9] is often the clearest and fastest choice. If you want any Unicode digit recognized by .NET, \d is more correct.

Why [0-9] and [0123456789] are similar

Regex engines usually optimize character classes internally. Even if you write all ten digits explicitly, the engine may compile that set into an efficient internal representation. So the difference between a range and an explicit set may be negligible.

Why \d may be slower

A typical reason is that the engine must ask something like:

  • “Is this character in the Unicode digit category?”

instead of just checking:

  • “Is this character between '0' and '9'?”

That extra flexibility can cost time, especially when repeated across many characters in many strings.

Mental Model

Think of these patterns like ways of checking IDs at a door:

  • [0-9] is like saying: only let in people wearing badges numbered 0 through 9.
  • [0123456789] is like keeping a printed list of the same ten badge numbers.
  • \d is like saying: let in anyone who counts as a digit according to the full international rulebook.

The first two checks are very simple. The third check may require consulting a larger set of rules.

So \d is more flexible, but flexibility can mean extra work.

Take Quiz

Syntax and Examples

Core syntax

var asciiRange = new Regex("[0-9]");
var explicitSet = new Regex("[0123456789]");
var digitClass = new Regex(@"\d");

Basic example

using System;
using System.Text.RegularExpressions;

class Demo
{
    static void Main()
    {
        string input = "Room 7";

        Console.WriteLine(Regex.IsMatch(input, "[0-9]"));   // True
        Console.WriteLine(Regex.IsMatch(input, "[0123456789]")); // True
        Console.WriteLine(Regex.IsMatch(input, @"\d"));     // True
    }
}

All three patterns match 7 here.

Important difference in meaning

using System;
using System.Text.RegularExpressions;

class Demo
{
    static void Main()
    {
        string arabicIndicDigit = "\u0661"; // Arabic-Indic digit one

        Console.WriteLine(Regex.IsMatch(arabicIndicDigit, "[0-9]")); // False
        Console.WriteLine(Regex.IsMatch(arabicIndicDigit, @"\d"));   // Often True in .NET
    }
}

This is the key idea:

  • [0-9] only matches ASCII digits.
  • \d can match a broader set of Unicode digits.

If you want ASCII-only shorthand-like behavior

In many codebases, developers simply use [0-9] when they want to be explicit.

bool hasAsciiDigit = Regex.IsMatch("A1B", "[0-9]");

That removes ambiguity about what counts as a digit.

Step by Step Execution

Example

using System;
using System.Text.RegularExpressions;

class Demo
{
    static void Main()
    {
        string input = "abc5xyz";
        var regex = new Regex("[0-9]");

        bool found = regex.IsMatch(input);
        Console.WriteLine(found);
    }
}

What happens step by step

  1. input is set to "abc5xyz".
  2. The regex pattern [0-9] is compiled into a rule that checks whether a character is between '0' and '9'.
  3. IsMatch starts scanning from the beginning of the string.
  4. It checks 'a':
    • not in the range 0 to 9
  5. It checks 'b':
    • not in the range 0 to 9
  6. It checks 'c':
    • not in the range 0 to 9
  7. It checks '5':
    • this is in the range 0 to 9
  8. The engine stops early because it found a match.
  9. found becomes true.
  10. True is printed.

Same idea with \d

var regex = new Regex(@"\d");

The scanning process is similar, but the character test may be more complex because the engine may use Unicode digit classification instead of a simple ASCII range check.

Real World Use Cases

When [0-9] is a good choice

Use [0-9] when your input format is explicitly ASCII-based:

  • ZIP codes like 12345
  • invoice numbers with Western digits
  • version strings such as v2
  • parsing machine-generated logs
  • validating numeric IDs from systems that only emit ASCII

Example:

bool containsAsciiDigit = Regex.IsMatch("OrderA7", "[0-9]");

When \d is a better choice

Use \d when you want to accept digits more generally under Unicode rules:

  • internationalized user input
  • apps that support multiple scripts
  • text processing where digit characters may come from different writing systems

Example:

bool containsAnyDigit = Regex.IsMatch(userInput, @"\d");

When performance matters

In hot paths such as:

  • processing millions of records
  • scanning large log files
  • validating high-throughput API input

small regex differences can matter. In those cases, benchmark the exact scenario and choose the narrowest pattern that matches your real requirements.

Take Quiz

Real Codebase Usage

In real projects, developers usually choose patterns based on intent first and performance second.

Common patterns

Explicit validation

If a field must contain ASCII digits only, teams often prefer [0-9] because it is unambiguous.

bool isValidProductCode = Regex.IsMatch(code, "^[0-9]+$");

Guard clauses

Regex is often used early in a method to reject invalid input.

if (!Regex.IsMatch(id, "^[0-9]+$"))
{
    throw new ArgumentException("ID must contain ASCII digits only.");
}

Configuration and parsing

Developers often use simple classes instead of broad shorthand when parsing config files or protocol formats.

var match = Regex.Match(line, "port=([0-9]+)");

Error handling and domain rules

If an application must support localized input, \d may be the right semantic choice.

if (!Regex.IsMatch(input, @"^\d+$"))
{
    return "Please enter digits.";
}

Practical advice

  • Use [0-9] when you mean ASCII digits only.
  • Use \d when you mean Unicode digits.
  • Do not assume shorthand and explicit classes are identical across regex engines.

Common Mistakes

1. Assuming \d always means [0-9]

This is one of the most common mistakes.

Regex.IsMatch("\u0661", @"\d");   // may be True
Regex.IsMatch("\u0661", "[0-9]");  // False

How to avoid it

Decide whether you want:

  • ASCII digits only: use [0-9]
  • Unicode digits: use \d

2. Benchmarking too little work

Very small benchmarks can be noisy because of:

  • JIT compilation
  • memory allocation
  • CPU cache effects
  • regex startup cost

How to avoid it

  • warm up the code first
  • run multiple iterations
  • isolate regex construction from matching
  • use a proper benchmarking tool when possible

3. Recreating regex objects unnecessarily

Broken approach:

foreach (var str in strings)
{
    var regex = new Regex("[0-9]");
    if (regex.IsMatch(str))
    {
        // ...
    }
}

This repeatedly constructs the regex.

Better:

var regex = new Regex("[0-9]");
foreach (var str in strings)
{
    if (regex.IsMatch(str))
    {
        // ...
    }
}

4. Using regex when a simple character check would do

If you only need to know whether a string contains an ASCII digit, regex may be overkill.

bool hasDigit = false;
foreach (char ch in input)
{
    if (ch >= '0' && ch <= '9')
    {
        hasDigit = true;
        break;
    }
}

How to avoid it

Use regex when pattern matching is the right tool, not automatically.

5. Confusing readability with performance

Some developers assume shorter syntax is always faster. That is not guaranteed.

  • \d is shorter
  • [0-9] is narrower in meaning
  • either may be better depending on the requirement

Comparisons

PatternMeaning in C# regexTypical scopePerformance tendencyBest use
\dDigit character classUnicode-aware digit matchingMay be slower than ASCII range checksInternationalized digit matching
[0-9]ASCII digits 0 through 9Narrow and explicitOften very fastASCII-only validation
[0123456789]Explicit list of ASCII digitsSame practical meaning as [0-9]Usually similar to [0-9]Rarely needed unless you prefer explicit listing

[0-9] vs [0123456789]

These usually behave the same for matching ASCII digits. Most regex engines optimize both forms, so performance differences are often tiny.

\d vs [0-9]

The key difference is semantics:

  • \d = digit class
  • [0-9] = ASCII range only

If semantics differ, performance can differ too.

Regex vs manual character scan

ApproachProsCons
RegexExpressive, concise, easy to extendMore overhead
Manual scanOften fastest for simple checksLess flexible for complex patterns

For a simple “contains a digit” check, manual scanning can be simpler and faster than regex.

Take Quiz

Cheat Sheet

Quick reference

Digit-related patterns in C# regex

@"\d"       // digit character class, Unicode-aware
"[0-9]"    // ASCII digits only
"[0123456789]" // explicit ASCII digit set

Key rule

  • \d is not always equivalent to [0-9] in .NET.

Choose this when...

  • Use \d when you want Unicode digits.
  • Use [0-9] when you want ASCII digits only.

Performance notes

  • [0-9] may be faster because it is a simple range check.
  • [0123456789] is often optimized similarly to [0-9].
  • \d may do broader character classification.

Common examples

Regex.IsMatch("abc5", "[0-9]")   // True
Regex.IsMatch("abc5", @"\d")     // True
Regex.IsMatch("\u0661", "[0-9]") // False
Regex.IsMatch("\u0661", @"\d")  // Often True

Best practice

Write the pattern that matches your actual requirement, then benchmark if performance matters.

FAQ

Why is \d slower than [0-9] in C#?

Because \d usually means a broader Unicode digit class, while [0-9] is only a simple ASCII range check.

Is [0123456789] slower than [0-9]?

Usually not by much. Regex engines often optimize both patterns into similar internal checks.

Does \d always match only 0 through 9?

No. In .NET, \d is generally Unicode-aware and can match digit characters beyond ASCII 0 to 9.

Which should I use: \d or [0-9]?

Use [0-9] for ASCII-only input. Use \d when you want to support Unicode digits.

Is regex the fastest way to check whether a string contains a digit?

Not always. For simple checks, a manual character loop can be faster and easier to reason about.

Why do benchmark results vary between runs?

Because of JIT compilation, CPU state, memory effects, and measurement noise. Small timing differences are not always meaningful.

Should I optimize regex syntax first?

Only if profiling shows regex is a bottleneck. Correctness and clarity usually matter more than tiny performance gains.

Take Quiz

Mini Project

Description

Build a small C# console program that checks whether input strings contain ASCII digits or Unicode digits, then compares the behavior of [0-9] and \d. This project helps you see that the two patterns can produce different results, which is important in validation and internationalized applications.

Goal

Create a program that tests multiple strings against both [0-9] and \d and prints which pattern matches each input.

Requirements

  • Create a list of sample strings, including one with ASCII digits and one with a Unicode digit.
  • Test each string using both [0-9] and \d.
  • Print the input and whether each regex matched.
  • Include at least one example where the two patterns produce different results.
Take Quiz

Keep learning

Related questions

View more