Overview
Modern C# has evolved control flow with pattern matching, switch expressions, and enhanced foreach capabilities. Understanding these features leads to more readable and maintainable code.
Core Concepts
Conditional Statements
// Complex conditions
bool isValid = age >= 18 && hasLicense && !isSuspended;
if (isValid)
{
// Allow driving
}
// Nested conditions (avoid when possible)
if (user != null)
{
if (user.IsActive)
{
if (user.HasPermission("admin"))
{
// Do something
}
}
}
// Better: Guard clauses
if (user == null) return;
if (!user.IsActive) return;
if (!user.HasPermission("admin")) return;
// Do something
// Null-conditional in conditions
if (user?.IsActive == true)
{
// Safe null check
}
Switch Statements and Expressions
// Traditional switch
switch (dayOfWeek)
{
case DayOfWeek.Saturday:
case DayOfWeek.Sunday:
Console.WriteLine("Weekend");
break;
case DayOfWeek.Monday:
Console.WriteLine("Start of week");
break;
default:
Console.WriteLine("Weekday");
break;
}
// Switch expression (C# 8+)
string message = dayOfWeek switch
{
DayOfWeek.Saturday or DayOfWeek.Sunday => "Weekend",
DayOfWeek.Monday => "Start of week",
_ => "Weekday"
};
// With multiple values
int quarter = month switch
{
1 or 2 or 3 => 1,
4 or 5 or 6 => 2,
7 or 8 or 9 => 3,
10 or 11 or 12 => 4,
_ => throw new ArgumentOutOfRangeException(nameof(month))
};
// Relational patterns
string category = score switch
{
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
>= 60 => "D",
_ => "F"
};
Pattern Matching
// Type patterns
object value = GetValue();
if (value is string text)
{
Console.WriteLine($"String length: {text.Length}");
}
else if (value is int number)
{
Console.WriteLine($"Number: {number}");
}
// Switch with type patterns
string Describe(object obj) => obj switch
{
null => "Nothing",
string s => $"String of length {s.Length}",
int n when n > 0 => $"Positive number: {n}",
int n => $"Non-positive number: {n}",
IEnumerable<int> list => $"List with {list.Count()} items",
_ => $"Unknown type: {obj.GetType().Name}"
};
// Property patterns
bool IsWeekendAfternoon(DateTime dt) => dt is
{
DayOfWeek: DayOfWeek.Saturday or DayOfWeek.Sunday,
Hour: >= 12 and < 18
};
// Positional patterns (with deconstruction)
string DescribePoint(Point point) => point switch
{
(0, 0) => "Origin",
(var x, 0) => $"On X-axis at {x}",
(0, var y) => $"On Y-axis at {y}",
(var x, var y) when x == y => $"On diagonal at {x}",
(var x, var y) => $"Point at ({x}, {y})"
};
Loop Variations
// for loop with multiple variables
for (int i = 0, j = 10; i < j; i++, j--)
{
Console.WriteLine($"i={i}, j={j}");
}
// Infinite loop with break
while (true)
{
var input = Console.ReadLine();
if (input == "quit") break;
Process(input);
}
// do-while (executes at least once)
string? userInput;
do
{
Console.Write("Enter a number: ");
userInput = Console.ReadLine();
} while (!int.TryParse(userInput, out _));
// foreach with index
foreach (var (item, index) in items.Select((x, i) => (x, i)))
{
Console.WriteLine($"{index}: {item}");
}
// Parallel foreach (when order doesn't matter)
Parallel.ForEach(items, item =>
{
ProcessItem(item);
});
break, continue, return
// break - exits the loop
for (int i = 0; i < 100; i++)
{
if (i == 10) break; // Exits at 10
Console.WriteLine(i);
}
// continue - skips to next iteration
for (int i = 0; i < 10; i++)
{
if (i % 2 == 0) continue; // Skip even numbers
Console.WriteLine(i); // 1, 3, 5, 7, 9
}
// Labeled statements (rare, avoid if possible)
outerLoop:
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
if (SomeCondition(i, j))
{
goto outerLoop; // Breaks out of inner loop
}
}
}
// Better alternative: extract to method
bool ProcessMatrix()
{
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
if (SomeCondition(i, j))
{
return true; // Clean exit
}
}
}
return false;
}
Guard Clauses and Early Returns
// Without guard clauses (deep nesting)
public decimal CalculateDiscount(Customer customer, Order order)
{
if (customer != null)
{
if (customer.IsActive)
{
if (order != null)
{
if (order.Total > 100)
{
return order.Total * 0.1m;
}
}
}
}
return 0;
}
// With guard clauses (flat and readable)
public decimal CalculateDiscount(Customer? customer, Order? order)
{
if (customer is null) return 0;
if (!customer.IsActive) return 0;
if (order is null) return 0;
if (order.Total <= 100) return 0;
return order.Total * 0.1m;
}
Interview Tips
Tip 1: Know when to use switch expressions vs traditional switch statements. Expressions are more concise for value-returning scenarios.
Tip 2: Understand that pattern matching can replace many if-else chains with more readable code.
Tip 3: Be ready to refactor nested conditions into guard clauses - this is a common interview task.
Common Interview Questions
-
What's the difference between break and continue?
breakexits the loop entirely.continueskips the rest of the current iteration and moves to the next. Both only affect the innermost loop they're in.
-
When would you use a for loop vs foreach?
for: When you need the index, need to modify the collection, or iterate in reverse.foreach: When you just need to process each item.foreachis cleaner and prevents off-by-one errors.
-
What is pattern matching in C#?
- Pattern matching tests values against patterns and extracts data. Types: type patterns (
is string s), property patterns (is { Length: > 0 }), relational patterns (is >= 10), positional patterns. Available in if statements and switch.
- Pattern matching tests values against patterns and extracts data. Types: type patterns (
-
What's the difference between switch statement and switch expression?
- Statement: Multiple statements per case, needs break, no return value. Expression: Single expression per arm, returns a value, exhaustiveness checking, more concise. Use expressions when returning values.
-
How do you exit nested loops?
- Options: Labeled goto (avoid), extract to method and return, boolean flag, or restructure logic. Extracting to a method with early return is usually cleanest.
-
What is a guard clause?
- Early return that handles edge cases at the start of a method. Reduces nesting, improves readability, and makes the "happy path" clear. Common pattern: check for null, invalid input, or preconditions first.
-
Explain the ternary operator.
condition ? valueIfTrue : valueIfFalse. Short form of if-else that returns a value. Keep it simple - if complex, use regular if-else for readability.