In Module 1 you learned to declare variables, work with arrays, and manipulate strings, but all the code you wrote so far always ran the same way, line after line, with no room to react to data. In real life, BiblioTechConsole needs to make decisions: can this book be lent out? Does the member already have too many active loans? Is the entered ISBN in a valid format? This lesson introduces conditional statements, the mechanism a C# program uses to decide which path to follow depending on whether certain conditions are met. Together with the loops you'll see in the next lesson, they are the foundation of any real business logic.
Content
- The
ifstatement else ifandelse: chaining conditions- Comparison operators
- Logical operators:
&&,||,! - The ternary operator
?: - Nesting conditionals and best practices
- Null coalescing in conditions:
??and??= - Full application: can the book be lent?
- The
if statement
if statementThe if statement evaluates a boolean expression (true or false) and executes a block of
code only if that expression is true.
Key points:
- The condition goes between parentheses
( )and must be an expression of typebool. - The code block goes between braces
{ }. If the block has a single statement, the braces are optional, but it's recommended to always keep them: it improves readability and avoids mistakes when a second line is added to the block later on. - If the condition is
false, the block is simply skipped and the program continues with the next statement.
int pages = 350;
// Without braces: it works, but it's not recommended
if (pages > 300)
Console.WriteLine("This is a lengthy book.");
else if and else: chaining conditions
else if and else: chaining conditionsWhen there is more than one possible path, conditions are chained with else if, and else
is reserved for the "in any other case" scenario.
int memberActiveLoans = 3;
if (memberActiveLoans == 0)
{
Console.WriteLine("The member has no active loans.");
}
else if (memberActiveLoans < 5)
{
Console.WriteLine("The member still has room to borrow more books.");
}
else
{
Console.WriteLine("The member has reached the loan limit.");
}The flow is evaluated from top to bottom and stops at the first condition that is true.
This matters: even though memberActiveLoans < 5 would also be true when the value is 0, the
first condition (== 0) has already captured that case, so the remaining else if blocks
aren't even evaluated.
| Structure | How many blocks run? | When to use it |
|---|---|---|
if alone |
0 or 1 | A single optional condition |
if/else |
Always 1 | Two mutually exclusive paths |
if/else if/else |
Always 1 (the first one that matches) | Several mutually exclusive paths, ordered by priority |
- Comparison operators
To build conditions you need operators that compare values and return a bool.
| Operator | Meaning | Example (pages = 350) |
Result |
|---|---|---|---|
== |
Equal to | pages == 350 |
true |
!= |
Not equal to | pages != 350 |
false |
> |
Greater than | pages > 300 |
true |
< |
Less than | pages < 300 |
false |
>= |
Greater than or equal | pages >= 350 |
true |
<= |
Less than or equal | pages <= 349 |
false |
A very common mistake for beginners is confusing = (assignment) with == (comparison).
if (available = true) doesn't compare anything: it assigns true to available, and it
also won't compile if available isn't of a bool-compatible type for that assignment in
that context. Always pay close attention to this detail.
Strings can also be compared with ==, since string overloads that operator to compare
content (rather than the memory reference):
string searchedTitle = "Hopscotch";
string catalogTitle = "Hopscotch";
if (searchedTitle == catalogTitle)
{
Console.WriteLine("Match found in the catalog.");
}
- Logical operators:
&&, ||, !
&&, ||, !When a decision depends on several conditions at once, they are combined with logical operators.
| Operator | Name | True when... |
|---|---|---|
&& |
Logical AND | both conditions are true |
| ` | ` | |
! |
Negation (NOT) | inverts the value of the condition |
bool available = true;
int memberActiveLoans = 2;
const int LoanLimit = 5;
if (available && memberActiveLoans < LoanLimit)
{
Console.WriteLine("Loan approved.");
}
else
{
Console.WriteLine("Loan denied.");
}! inverts a boolean condition, which often makes code more readable than comparing
explicitly against false:
bool isDelinquent = false;
if (!isDelinquent)
{
Console.WriteLine("The member has no pending penalties.");
}An important performance and safety detail is short-circuit evaluation: in a && b, if
a is already false, C# never evaluates b, because the result is already decided. The
same happens with a || b if a is already true. This lets you write safe conditions like
the following, where the second part is only evaluated if the first one guarantees there will
be no error:
string[] bookTitles = { "One Hundred Years of Solitude", "Hopscotch", "Ficciones" };
int index = 5;
if (index < bookTitles.Length && bookTitles[index] == "Hopscotch")
{
Console.WriteLine("Found.");
}
else
{
Console.WriteLine("Index out of range or different title.");
}If index < bookTitles.Length is false (as in this case, with only 3 elements and an index
value of 5), C# never gets to access bookTitles[index], thus avoiding an index-out-of-range
exception.
- The ternary operator
?:
?:The ternary operator condenses a simple, single-expression if/else into a single line.
Its form is:
bool available = true;
string status = available ? "Available" : "On loan";
Console.WriteLine($"Book status: {status}");This is equivalent to:
The ternary operator is ideal when the result is a simple value that will be assigned or displayed directly. It's best not to overuse it for complex logic, nor to nest several ternaries inside one another, because the code becomes hard to read:
// Avoid this: two nested ternaries are hard to read at a glance
string category = pages > 500 ? "Long" : pages > 200 ? "Medium" : "Short";In those cases an explicit if/else if/else is preferable, or the switch you'll see in
the next lesson of this module.
- Nesting conditionals and best practices
It's common to need a condition inside another condition:
bool available = true;
int memberActiveLoans = 4;
const int LoanLimit = 5;
if (available)
{
if (memberActiveLoans < LoanLimit)
{
Console.WriteLine("Loan approved.");
}
else
{
Console.WriteLine("The member has reached their loan limit.");
}
}
else
{
Console.WriteLine("The book is not currently available.");
}This code works, but as the nesting grows (three, four levels), it becomes hard to follow. Two techniques help keep it under control:
Combining conditions with logical operators instead of nesting, when the final result is the same:
if (available && memberActiveLoans < LoanLimit)
{
Console.WriteLine("Loan approved.");
}
else
{
Console.WriteLine("Loan denied.");
}Inverting the condition and returning as early as possible (early return or guard
clauses), very useful inside methods or local functions: instead of nesting the "good" case
inside several if blocks, the cases that prevent continuing are discarded first.
string EvaluateLoan(bool available, int memberActiveLoans, int loanLimit)
{
if (!available)
{
return "The book is not available.";
}
if (memberActiveLoans >= loanLimit)
{
return "The member has reached their loan limit.";
}
return "Loan approved.";
}This second version avoids nesting entirely, and each condition reads independently, which makes it easier to add new rules in the future (for example, checking whether the member has pending penalties) without restructuring the whole block.
- Null coalescing in conditions:
?? and ??=
?? and ??=In the variables lesson you already saw that a reference type, such as string, can be
null. The ?? (null-coalescing) and ??= (null-coalescing assignment) operators are very
useful for making decisions when a piece of data might be missing.
?? returns the left-hand operand if it's not null, or the right-hand one otherwise:
string? enteredIsbn = null;
string isbnToShow = enteredIsbn ?? "ISBN not available";
Console.WriteLine(isbnToShow); // ISBN not availableThis avoids having to write the longer equivalent:
string isbnToShow;
if (enteredIsbn != null)
{
isbnToShow = enteredIsbn;
}
else
{
isbnToShow = "ISBN not available";
}??= assigns a default value only if the variable is currently null, and does nothing
if it already has a value:
string? memberNote = null;
memberNote ??= "No incidents recorded";
Console.WriteLine(memberNote); // No incidents recorded
memberNote ??= "This text is never assigned";
Console.WriteLine(memberNote); // Still shows: No incidents recordedThese operators can also be combined directly inside an if condition:
string? userSearchTerm = null;
if ((userSearchTerm ?? "").Length > 0)
{
Console.WriteLine("Searching for: " + userSearchTerm);
}
else
{
Console.WriteLine("No search term was entered.");
}
- Full application: can the book be lent?
Let's bring all of the above together in an example close to what BiblioTechConsole will actually need: deciding whether a book can be lent based on its availability and the number of active loans the member has.
bool bookAvailable = true;
int memberActiveLoans = 4;
const int LoanLimitPerMember = 5;
bool memberSanctioned = false;
bool canLendBook = bookAvailable
&& !memberSanctioned
&& memberActiveLoans < LoanLimitPerMember;
string message = canLendBook
? "Loan approved: you can hand over the copy."
: "Loan denied.";
Console.WriteLine(message);
if (!canLendBook)
{
if (!bookAvailable)
{
Console.WriteLine("Reason: no copies are currently available.");
}
else if (memberSanctioned)
{
Console.WriteLine("Reason: the member has an active sanction.");
}
else
{
Console.WriteLine("Reason: the member has reached the loan limit.");
}
}Notice the design: a single boolean expression (canLendBook) concentrates the main business
rule by combining && and !, the ternary decides the main message, and only if the loan is
denied does a second if/else if/else block run to explain the exact reason. This is a
much more maintainable structure than nesting the three if blocks inside one another.
Common Mistakes and Tips
- Confusing
=with==. This is the most frequent mistake among beginners. The C# compiler usually catches it if the types don't match, but it's worth double-checking at a glance every time. - Nesting too many levels of
if. If you find yourself with three or more levels of indentation, it can almost always be simplified by combining conditions with&&/||or applying the early-return (guard clause) pattern. - Forgetting the
elsewhen it's actually needed. If a variable must always end up with a value assigned, make sure every possible path is covered. - Overusing the ternary operator for complex or nested logic. Reserve it for simple, single-value decisions.
- Not checking for
nullbefore operating on a reference-type variable. Use??to provide a default value instead of risking aNullReferenceException(you'll see exceptions in detail in the last lesson of this module). - Comparing strings without accounting for case when the user enters free text. Remember
ToLower()(seen in the previous lesson) if you want a case-insensitive comparison.
Exercises
-
Declare three variables:
int pages,bool available, andint memberActiveLoans. Write anif/else if/elsethat prints to the console:"Not available"ifavailableisfalse."Long loan (over 400 pages)"if it's available andpages > 400."Regular loan"in any other case where it's available.
-
Using the ternary operator, create a
string resultvariable that holds"Member at limit"ifmemberActiveLoans >= 5, or"Member with room to spare"otherwise. Print the result to the console. -
Declare
string? memberEmail = null;. Use??to print the email if it exists, or the text"No email on file"if it'snull. Then use??=to assign it the value"contact@bibliotech.local"only if it's stillnull, and print the final value.
Solutions
int pages = 550;
bool available = true;
int memberActiveLoans = 2;
if (!available)
{
Console.WriteLine("Not available");
}
else if (pages > 400)
{
Console.WriteLine("Long loan (over 400 pages)");
}
else
{
Console.WriteLine("Regular loan");
}
With pages = 550 and available = true, the result printed is "Long loan (over 400 pages)". Note that !available is checked first: this way, if the book weren't
available, the number of pages wouldn't even be examined.
int memberActiveLoans = 5;
string result = memberActiveLoans >= 5 ? "Member at limit" : "Member with room to spare";
Console.WriteLine(result);
With memberActiveLoans = 5, the result printed is "Member at limit", since the >= 5
condition is met exactly at the limit.
string? memberEmail = null;
string emailToShow = memberEmail ?? "No email on file";
Console.WriteLine(emailToShow); // No email on file
memberEmail ??= "contact@bibliotech.local";
Console.WriteLine(memberEmail); // contact@bibliotech.local
The first line doesn't modify memberEmail: it only computes an alternative value to
display. The second line (??=) does modify the variable, assigning the default value
because it was still null at that point.
Conclusion
In this lesson you've learned to make decisions in your code with if/else if/else, to
build conditions with comparison and logical operators, to simplify simple assignments with
the ternary operator ?:, to keep nesting under control by combining conditions or applying
early returns, and to use ??/??= to work with data that might be missing. With this,
BiblioTechConsole can now decide whether a loan is valid according to several business rules
at once.
However, making a single decision isn't enough when an action needs to be repeated many
times: going through the entire book catalog, counting how many are available, or searching
for one by title until it's found. In the next lesson, Loops, you'll learn to repeat
instructions with for, while, do-while, and foreach, and to control that repetition
with break and continue.
C# Programming Course
Module 1: Introduction to C#
- Introduction to C#
- Setting Up the Development Environment
- Hello World Program
- Basic Syntax and Structure
- Variables and Data Types
- Arrays and Strings
Module 2: Control Structures
Module 3: Object-Oriented Programming
- Classes and Objects
- Methods
- Constructors and Destructors
- Inheritance
- Polymorphism
- Encapsulation
- Abstraction
- Structs and Records: Value Types and Reference Types
Module 4: Advanced C# Concepts
- Interfaces
- Delegates and Events
- Pattern Matching and Modern C# Features
- Generics
- Collections
- LINQ (Language Integrated Query)
- Asynchronous Programming
Module 5: Working with Data
- File I/O
- Serialization
- Database Connectivity
- Entity Framework
- Working with JSON and Consuming REST APIs
Module 6: Advanced Topics
- Reflection
- Attributes
- Dynamic Programming
- Memory Management and Garbage Collection
- Multithreading and Parallel Programming
Module 7: Building Applications
Module 8: Best Practices and Design Patterns
- Coding Standards and Best Practices
- Design Patterns
- Dependency Injection and Inversion of Control
- Unit Testing
- Code Review and Refactoring
