When Switch Statements Get Out of Hand

I remember refactoring a legacy order processing system where a single method had grown to over 300 lines, mostly filled with nested if-else chains checking order states, payment statuses, and customer tiers. It was brittle, hard to test, and every new requirement felt like defusing a bomb. That’s when I started leaning harder into C# pattern matching — not just the basic switch expressions, but combining them with records and positional deconstruction to turn complex conditional logic into something readable and maintainable.

The Problem: Branching Logic Hell

Consider a shipping calculator that needs to determine rates based on package weight, destination zone, and customer loyalty tier. A naive implementation might look like this:

public decimal CalculateShippingRate(Order order)
{
    if (order.Weight > 50)
    {
        if (order.Destination == "International")
        {
            return order.IsPremiumCustomer ? 25.00m : 40.00m;
        }
        else
        {
            return order.IsPremiumCustomer ? 15.00m : 25.00m;
        }
    }
    else
    {
        if (order.Destination == "International")
        {
            return order.IsPremiumCustomer ? 10.00m : 15.00m;
        }
        else
        {
            return order.IsPremiumCustomer ? 5.00m : 10.00m;
        }
    }
}

This works, but it’s painful to read, easy to break when modifying, and nearly impossible to unit test all paths without duplication. As requirements grow — say, adding holiday surcharges or regional overrides — this approach collapses under its own weight.

The Fix: Pattern Matching with Records

C# 9 introduced records, which are perfect for modeling data carriers like our order details. Combined with pattern matching switch expressions, we can replace nested conditionals with a clear, top-down decision tree that’s both exhaustive and easy to extend.

public record ShippingCriteria(double WeightKg, string DestinationZone, bool IsPremium);

public decimal CalculateShippingRate(Order order)
{
    var criteria = new ShippingCriteria(
        order.WeightKg,
        order.DestinationZone,
        order.IsPremiumCustomer
    );

    return criteria switch
    {
        // Heavy international shipments
        { WeightKg: > 50, DestinationZone: "International" } =>
            order.IsPremiumCustomer ? 25.00m : 40.00m,

        // Heavy domestic
        { WeightKg: > 50, DestinationZone: "Domestic" } =>
            order.IsPremiumCustomer ? 15.00m : 25.00m,

        // Light international
        { WeightKg: <= 50, DestinationZone: "International" } =>
            order.IsPremiumCustomer ? 10.00m : 15.00m,

        // Light domestic (fallback)
        { WeightKg: <= 50, DestinationZone: "Domestic" } =>
            order.IsPremiumCustomer ? 5.00m : 10.00m,

        // Catch-all for invalid zones — fail fast
        { DestinationZone: var zone } => throw new ArgumentException(
            $