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

  1. The if statement
  2. else if and else: chaining conditions
  3. Comparison operators
  4. Logical operators: &&, ||, !
  5. The ternary operator ?:
  6. Nesting conditionals and best practices
  7. Null coalescing in conditions: ?? and ??=
  8. Full application: can the book be lent?

  1. The if statement

The if statement evaluates a boolean expression (true or false) and executes a block of code only if that expression is true.

bool available = true;

if (available)
{
    Console.WriteLine("The book is available for loan.");
}

Key points:

  • The condition goes between parentheses ( ) and must be an expression of type bool.
  • 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.");

  1. else if and else: chaining conditions

When 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

  1. 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.");
}

  1. 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.

  1. The ternary operator ?:

The ternary operator condenses a simple, single-expression if/else into a single line. Its form is:

condition ? valueIfTrue : valueIfFalse
bool available = true;
string status = available ? "Available" : "On loan";

Console.WriteLine($"Book status: {status}");

This is equivalent to:

string status;
if (available)
{
    status = "Available";
}
else
{
    status = "On loan";
}

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.

  1. 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.

  1. Null coalescing in conditions: ?? 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 available

This 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 recorded

These 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.");
}

  1. 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 else when 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 null before operating on a reference-type variable. Use ?? to provide a default value instead of risking a NullReferenceException (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

  1. Declare three variables: int pages, bool available, and int memberActiveLoans. Write an if/else if/else that prints to the console:

    • "Not available" if available is false.
    • "Long loan (over 400 pages)" if it's available and pages > 400.
    • "Regular loan" in any other case where it's available.
  2. Using the ternary operator, create a string result variable that holds "Member at limit" if memberActiveLoans >= 5, or "Member with room to spare" otherwise. Print the result to the console.

  3. Declare string? memberEmail = null;. Use ?? to print the email if it exists, or the text "No email on file" if it's null. Then use ??= to assign it the value "contact@bibliotech.local" only if it's still null, 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.

© Copyright 2026. All rights reserved