Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C# & .NET (C# 14, .NET 10 LTS)  Module 10 – Pattern matching

Relational, logical and list patterns

Compare values with C# relational patterns, combine tests with and, or and not, and match arrays, lists and spans by their shape with list and slice patterns.

  • Intermediate
  • 20 minutes
  • Examples run with .NET 10.0.401 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 8 of 20 minutes free
Free: Relational and logical patterns compare with constants and combine tests · A list pattern matches a sequence by its length and its elements. With a pass: 2 in-depth parts and 3 interview answers, about 12 minutes.

What you will learn

  • Match ranges of values with relational patterns and combine them with and, or and not
  • Predict how not, and and or group without brackets, and fix a test that groups wrongly
  • Match arrays, lists and strings by shape with list and slice patterns
  • Find the values a set of number ranges leaves uncovered, using the compiler's warnings
  • Parse text with list patterns on spans, and measure what a captured slice allocates

Before you start

What you’ll learn · Before you start

What you will learn

  • Match ranges of values with relational patterns and combine them with and, or and not
  • Predict how not, and and or group without brackets, and fix a test that groups wrongly
  • Match arrays, lists and strings by shape with list and slice patterns
  • Find the values a set of number ranges leaves uncovered, using the compiler's warnings
  • Parse text with list patterns on spans, and measure what a captured slice allocates

Before you start

On this page

A cold-room sensor reports 75.0 °C. The room should stay from 2 to 8 degrees and the sensor cannot measure above 60, so the reading is a fault, not a temperature. The test that flags it is one line of relational and logical patterns, and one pair of brackets decides whether it works.

Patterns can also test the shape of a sequence: how many arguments a command has, and which word comes first.

Relational and logical patterns compare with constants and combine tests

< 2 and <= 8 are relational patterns, met in the lesson on switch: they compare the value with a constant of a numeric, char or enum type. logical pattern: A pattern built from other patterns with and, or and not, such as >= 2 and <= 8 or not null, added in C# 9. not binds first, then and, then or; brackets change the grouping., added in C# 9 with relational ones, combine other patterns: and needs both to match, or either, and not turns a match into a miss. coldroom.cs sorts eight readings into bands, then tests each one for a fault twice, with brackets and without:

coldroom.cs: bands, and a fault test written two ways C# · coldroom/coldroom.cs
// Readings in degrees Celsius from a cold-room sensor. Relational patterns sort them into bands; logical patterns
// flag readings the sensor cannot really produce. The room should stay from 2 to 8; the sensor measures -30 to 60.
double[] readings = [4.2, 2.0, 1.5, 9.1, 12.0, -0.5, 75.0, double.NaN];

foreach (double celsius in readings)
{
    Console.WriteLine($"{celsius,5}  {Band(celsius),-10}  fault: {Fault(celsius),-5}  fault, written without brackets: {FaultSlip(celsius)}");
}

static string Band(double celsius) => celsius switch
{
    double.NaN => "no reading",
    < 0 => "freezing",
    < 2 => "too cold",
    <= 8 => "in range",
    <= 10 => "warm",
    _ => "too warm",
};

// A reading outside the sensor's range is a fault, not a temperature.
static bool Fault(double celsius) => celsius is not (>= -30.0 and <= 60.0);

// The same test without brackets: not binds to >= -30.0 alone, so this means (< -30.0 or NaN) and <= 60.0.
static bool FaultSlip(double celsius) => celsius is not >= -30.0 and <= 60.0;

Output

  4.2  in range    fault: False  fault, written without brackets: False
    2  in range    fault: False  fault, written without brackets: False
  1.5  too cold    fault: False  fault, written without brackets: False
  9.1  warm        fault: False  fault, written without brackets: False
   12  too warm    fault: False  fault, written without brackets: False
 -0.5  freezing    fault: False  fault, written without brackets: False
   75  too warm    fault: True   fault, written without brackets: False
  NaN  no reading  fault: True   fault, written without brackets: False

Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run coldroom.cs

Read the bands from the top. Every arm names only where its band ends, since whatever lies below that end was already taken by an arm above, so 2.0 fell into <= 8 and printed in range. NaN needed its own arm, double.NaN, a constant pattern, because a relational pattern never matches NaN. The two fault columns disagree on two readings, 75.0 and NaN.

The cause is how the combinators group. The C# reference gives the order: not binds first, then and, then or. Without brackets, not >= -30.0 and <= 60.0 means (not >= -30.0) and <= 60.0, which is not the range test it looks like. Step through both groupings for the two readings they disagree on:

Interactive · step by step Two ways to group not, and, and a range Step through how each test is grouped and what it gives for 75.0 and NaN. The left column is FaultSlip, written without brackets; the right is Fault.
Two ways to group not, and, and a range, step 6 of 6. Showing Without brackets, not >= -30.0 (NaN: true), and <= 60.0 (NaN fails: false), With brackets, >= -30.0 and <= 60.0 (NaN: false), not (result: true). In focus: >= -30.0 and <= 60.0, not.Without bracketsnot >= -30.0 and <= 60.0not >= -30.0NaN: trueand <= 60.0NaN fails: falseWith bracketsnot (>= -30.0 and <= 60.0)>= -30.0 and <= 60.0NaN: falsenotresult: true

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. not binds first. Pattern combinators bind in the order not, then and, then or. Without brackets, not takes only the pattern right after it, >= -30.0, and the and joins that result to <= 60.0.
  2. Brackets group the range. With brackets the range >= -30.0 and <= 60.0 is one pattern, and not flips its result: true for every reading outside the range the sensor can measure.
  3. 75.0 without brackets. 75.0 is at least -30.0, so not >= -30.0 is false, and false and anything is false. FaultSlip reports 75.0 as no fault.
  4. 75.0 with brackets. 75.0 is above 60.0, so the range is false, and not makes it true. Fault reports 75.0 as a fault, as it should.
  5. NaN without brackets. NaN fails every relational pattern. not >= -30.0 is therefore true, but <= 60.0 is false, so FaultSlip misses the missing reading as well.
  6. NaN with brackets. For NaN the range is false, because both its comparisons fail, and not makes it true: Fault flags the missing reading too.

Writing not before a range without brackets

celsius is not >= -30.0 and <= 60.0 negates only the first comparison, so 75.0 and NaN pass as no fault. Write celsius is not (>= -30.0 and <= 60.0): the brackets make the range one pattern, and not then covers all of it.

One more rule follows from or and not: a pattern under them cannot declare a variable, error CS8780, because the compiler could not tell whether it was assigned. The single exception is the one met in the first lesson, is not string text as the whole pattern of an is.

Checkpoint

Check: Relational and logical patterns, and how they group

  1. Question 1 of 2 In Band of coldroom.cs, the arms are < 0, < 2, <= 8, <= 10 and _. Which band does a reading of 2.0 fall into?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: in range: < 2 fails for 2.0, and the next arm, <= 8, matches

    With arms that state their tops, < leaves the top value to the next band and <= keeps it.

  2. Question 2 of 2 How does the compiler group x is not >= 10 and <= 20?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: (not >= 10) and <= 20, which is the same as < 10

    The combinators bind in the order not, and, or; brackets are the only way to make not cover a range.

A list pattern matches a sequence by its length and its elements

A list pattern: A pattern in square brackets, such as ["add", var item], that matches a sequence of that length element by element (C# 11). The compile-time type needs a Length or Count and an indexer., added in C# 11, is a list of patterns in square brackets. ["add", var item] matches a sequence of exactly two elements whose first is "add", and names the second. [] matches an empty sequence, and a slice pattern: The pattern .. inside a list pattern, at most once, standing for any number of elements, as in [first, .., last]. Followed by var or another pattern, it captures those elements as a slice., .., stands for any number of elements, at most once per list pattern. commands.cs reads the arguments of a shopping-list tool:

commands.cs: arguments matched by shape C# · commands/commands.cs
// The arguments of a shopping-list tool, matched by their shape with list patterns: how many there are, and what
// stands at each position.
string[][] calls =
[
    ["add", "mango"],
    ["add", "rice", "5", "kg"],
    ["remove", "mango"],
    ["list"],
    [],
    ["remove"],
    ["clear", "--yes"],
];

foreach (string[] call in calls)
{
    Console.WriteLine($"{Show(call),-27} {Describe(call)}");
}

static string Describe(string[] args) => args switch
{
    [] => "no command: print the help",
    ["add", var item] => $"add {item}",
    ["add", var item, .. var details] => $"add {item}, with {details.Length} more words: {string.Join(' ', details)}",
    ["remove", var item] => $"remove {item}",
    ["list"] => "list everything",
    [var command, ..] => $"unknown or incomplete: {command}",
};

static string Show(string[] args) => "[" + string.Join(", ", args.Select(a => $"\"{a}\"")) + "]";

Output

["add", "mango"]            add mango
["add", "rice", "5", "kg"]  add rice, with 2 more words: 5 kg
["remove", "mango"]         remove mango
["list"]                    list everything
[]                          no command: print the help
["remove"]                  unknown or incomplete: remove
["clear", "--yes"]          unknown or incomplete: clear

Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run commands.cs

Each call took the first arm whose shape fitted. ["add", "rice", "5", "kg"] was too long for ["add", var item], so it reached the arm with a slice, where .. var details captured the last two words. ["remove"] was too short for ["remove", var item] and fell to [var command, ..], which accepts any call with at least one word. Step through the long add call:

Interactive · step by step One call against two arms of Describe Step through the parts of each list pattern on the left against the tokens on the right. The length is tested before any element.
One call against two arms of Describe, step 5 of 5. Showing Arm: add with details, Length, Index 0: "add", Index 1: var item, .. var details, The call, Length 4 (4: at least 2), tokens[0] is "add", item = "rice", details: 2 tokens. In focus: .. var details, details: 2 tokens.Arm: add with detailsadd, an item, the restLengthat least 2Index 0: "add"Index 1: var item.. var detailsthe restThe calladd rice 5 kgLength 44: at least 2tokens[0] is "add"item = "rice"details: 2 tokens"5" and "kg"

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. The arm above fails. The arm ["add", var item] has no slice, so it needs exactly two tokens. The call has four, so that arm fails on its length before any token is compared.
  2. Length first. The next arm has two element patterns and a slice, so it needs at least two tokens. Four is enough.
  3. The command word. The constant pattern "add" is matched against the token at index 0. It is the same string, so this part matches.
  4. The item. var item matches any token and names the one at index 1: item is "rice".
  5. The slice. The slice takes every token between the patterns before and after it, here the last two. Captured with var, they are named details: the 2 tokens "5" and "kg".

List patterns work on arrays, List<T>, strings and spans: any type whose compile-time type has a Length or Count and an indexer. An IEnumerable<T> alone does not qualify, because the pattern reads positions instead of enumerating, and capturing a slice with var also needs a range indexer or a Slice method.

Pattern Tests Example Added in
relational a comparison with a constant <= 8 C# 9
logical a combination of patterns not (>= -30.0 and <= 60.0) C# 9
list a sequence’s length and elements ["add", var item, ..] C# 11

Two questions build on this: which values a set of number ranges leaves uncovered, which the compiler can name for you, and what a captured slice costs on strings, arrays and spans.

Checkpoint

Check: List and slice patterns

  1. Question 1 of 2 Which arm of Describe in commands.cs takes the call ["add", "mango"]?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: ["add", var item]: exactly two elements, the first "add"

    A list pattern without a slice needs the exact length; with a slice, at least the elements it names.

  2. Question 2 of 2 How many slice patterns, .., may one list pattern contain?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: At most one

    [first, .., last] has one slice in the middle; a second .. is a compile error.

Gaps in number ranges: what the compiler names, and the slip it cannot see Pro: opens with a Pro, Premium or Ultimate pass

A switch over numbers can leave values uncovered between its arms, and the compiler searches for them. This part builds four tests that draw its warnings and one that draws none, then gives the rules that close every gap.

In this part:

  • Four warnings over numbers, and one slip with none, built and run
  • Where each boundary falls, walked a tenth of a degree at a time
  • Writing bands that leave no gap

3 runnable examples (C#, Python) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Parsing with list patterns on strings and spans, with allocations measured Pro: opens with a Pro, Premium or Ultimate pass

A list pattern reads elements by position, so matching a sequence costs nothing extra, but capturing a slice can. This part counts the bytes three versions of one test allocate, then gives you a chat command parser to write.

In this part:

  • What a captured slice allocates, counted over 1,000 lines
  • A clock time checked character by character, without allocating
  • Exercise: chat commands into typed records

3 runnable examples (C#) · about 4 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Practise: relational, logical and list patterns

Practise the three kinds of pattern below. Every choice says why it is right or wrong, and the kind you are least sure of is asked about first:

Practice that adapts to you

Practise this lesson

8 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 8 double reading = double.NaN; Which of these patterns match it? Choose every one.

    Choose every answer that is right.

    Show the answer: Question 1 of 8

    Answer:

    • not < 0
    • double.NaN

    This is why Band needs a double.NaN arm, and why a not in front of a range catches NaN.

  2. Question 2 of 8 What can a relational pattern such as <= 8 compare?

    Choose one answer.

    Show the answer: Question 2 of 8

    Answer: A value of a numeric type, char or an enum, with a constant of that type

    To compare with a variable, use a when guard; to compare text, normalise it and use constant patterns.

  3. Question 3 of 8 What does Weekend(DayOfWeek.Monday) return?

    Read the code, then choose one answer.

    static bool Weekend(DayOfWeek d) =>
        d is not DayOfWeek.Saturday or DayOfWeek.Sunday;
    Show the answer: Question 3 of 8

    Answer: true, because the test is (not Saturday) or Sunday

    The intended test is d is DayOfWeek.Saturday or DayOfWeek.Sunday, or for the opposite, d is not (DayOfWeek.Saturday or DayOfWeek.Sunday).

  4. Question 4 of 8 Why does if (x is int n or long m) not compile?

    Choose one answer.

    Show the answer: Question 4 of 8

    Answer: A variable cannot be declared under or, because only one side can have matched (CS8780)

    Match each type in its own arm, or test x is int or long and convert the value afterwards.

  5. Question 5 of 8 What does the list pattern [] match?

    Choose one answer.

    Show the answer: Question 5 of 8

    Answer: A sequence with no elements

    [] tests the length alone, which is often clearer than { Length: 0 } inside a switch of list patterns.

  6. Question 6 of 8 Which of these can a list pattern match directly? Choose every one.

    Choose every answer that is right.

    Show the answer: Question 6 of 8

    Answer:

    • string
    • ReadOnlySpan<char>
    • List<int>
    • string[]

    The compiler decides from the compile-time type, so a List<int> held in an IEnumerable<int> variable cannot be matched with a list pattern.

  7. Question 7 of 8 A tool accepts deploy <env> and deploy <env> --dry-run, and must reject anything longer. Which arms say exactly that?

    Choose one answer.

    Show the answer: Question 7 of 8

    Answer: ["deploy", var env] and ["deploy", var env, "--dry-run"]

    Leave the slice out when the length is part of the rule; add it only where any number of elements is allowed.

  8. Question 8 of 8 Which pattern names the last element of a non-empty array?

    Choose one answer.

    Show the answer: Question 8 of 8

    Answer: [.., var last]

    A slice can stand at the start, in the middle or at the end; the element patterns around it fix their positions.

Interview questions

Fresher · What do the and, or and not patterns do, and in what order do they apply?

They combine patterns, added in C# 9 with relational patterns: and matches when both patterns match, or when either does, and not when its pattern does not. They bind in that order of strength, not first, then and, then or, so x is not >= 10 and <= 20 means (not >= 10) and <= 20, which is not a range test. Use brackets to group, as in x is not (>= 10 and <= 20). Unlike && and ||, they combine patterns, not Boolean expressions, and a variable cannot be declared under or.

  1. Mid to senior

    A cold-room alert fires when reading is not >= -30.0 and <= 60.0. It has never fired for an impossible reading, although the logs hold values of 75 and NaN, and its unit tests at -40 and 20 pass. Explain the bug, why the build gave no warning, and how you would test rules like this.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Senior to staff

    A service parses five million log lines a day with string.Split and Substring, and a colleague proposes rewriting the parser with list patterns. Would you? Explain what list patterns make simpler, where they do not fit, what they cost on strings and spans, and how you would measure the change.

    Model answer, Pro. First step and follow-ups

  3. Fresher to mid

    A delivery fee is a switch over whole kilometres with the arms < 5, >= 5 and < 10, and > 10. One order made it throw SwitchExpressionException in production. Which distance was it, why did the build not stop, and what would you change in the code and in the build?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Relational patterns compare with a constant, and each arm of a band switch states only its top; NaN needs its own double.NaN arm.
  • not binds before and, which binds before or, so brackets decide what a not covers.
  • A list pattern tests a sequence’s length and its elements by position, with one optional slice, .., for the elements in between.
  • List patterns need a Length or Count and an indexer on the compile-time type, so arrays, lists, strings and spans qualify and a plain IEnumerable<T> does not.

Terms in this lesson

list pattern
A pattern in square brackets, such as ["add", var item], that matches a sequence of that length element by element (C# 11). The compile-time type needs a Length or Count and an indexer.
logical pattern
A pattern built from other patterns with and, or and not, such as >= 2 and <= 8 or not null, added in C# 9. not binds first, then and, then or; brackets change the grouping.
slice pattern
The pattern .. inside a list pattern, at most once, standing for any number of elements, as in [first, .., last]. Followed by var or another pattern, it captures those elements as a slice.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.