C Programming Β· September 10, 2026

Relational, Logical, and Conditional Operators in C

Module I Β· Basics of C Β· Lecture 7


🎯 Why This Matters

Every decision a program ever makes β€” is this number bigger, is the user old enough, is a form completely filled in β€” depends on operators that answer true or false. This post covers the three tools C gives you for that: relational operators (comparing two values), logical operators (combining multiple comparisons), and the conditional operator (a compact shorthand for a simple if-else). Once you’re comfortable with all three, you’ll be able to construct and evaluate genuinely complex conditions with confidence β€” exactly what this lecture’s output-prediction activity is designed to test.

βš–οΈ Relational Operators

Relational operators compare two values and produce a result: 1 for true, 0 for false. C has no separate boolean type β€” these results are just ordinary ints.

OperatorMeaningExampleResult
==Equal to5 == 51
!=Not equal to5 != 31
>Greater than7 > 21
<Less than7 < 20
>=Greater than or equal to5 >= 51
<=Less than or equal to5 <= 40
#include <stdio.h>

int main() {
    int a = 10, b = 20;

    printf("a == b : %d\n", a == b);
    printf("a != b : %d\n", a != b);
    printf("a < b  : %d\n", a < b);
    printf("a >= b : %d\n", a >= b);

    return 0;
}

πŸ–₯️ Output:
a == b : 0
a != b : 1
a < b  : 1
a >= b : 0

⚠️ The single most common C bug: confusing = (assignment) with == (comparison). Writing a = 5 where you meant a == 5 doesn’t check whether a equals 5 β€” it assigns 5 to a, and since 5 is non-zero, the expression evaluates as true. This compiles without error and silently produces wrong behavior, which is exactly what makes it dangerous.

Comparing Variables Directly

#include <stdio.h>

int main() {
    int applesInBasketA = 12;
    int applesInBasketB = 15;

    printf("A has more: %d\n", applesInBasketA > applesInBasketB);
    printf("Same count: %d\n", applesInBasketA == applesInBasketB);
    printf("B has at least as many: %d\n", applesInBasketB >= applesInBasketA);

    return 0;
}

πŸ–₯️ Output:
A has more: 0
Same count: 0
B has at least as many: 1

Each comparison here reduces two whole numbers down to a single 0 or 1 β€” that result can be stored, printed, or (once you reach the next post) used to decide which branch of a program runs.

⚠️ The Trap: Comparing Floats for Equality

#include <stdio.h>

int main() {
    float result = 0.1 + 0.2;

    printf("result:        %.17f\n", result);
    printf("result == 0.3: %d\n", result == 0.3);

    return 0;
}

πŸ–₯️ Output:
result: 0.30000001192092896
result == 0.3: 0

This connects directly to the “Data Types in C” post: float can’t represent every decimal value exactly, so 0.1 + 0.2 ends up ever so slightly different from the literal 0.3 β€” and == checks for exact bit-for-bit equality, not “close enough.” Never use == to compare floating-point values directly. The standard fix is to check whether the difference is smaller than some tiny tolerance instead, e.g. (result - 0.3) < 0.0001 && (result - 0.3) > -0.0001 β€” a technique that will make more sense once we cover functions from <math.h> like fabs() later in this course.

πŸ”— Logical Operators

Logical operators combine multiple relational expressions into a single true/false result. C has three: && (AND), || (OR), and ! (NOT).

#include <stdio.h>

int main() {
    int age = 20;
    int hasID = 1;

    printf("age > 18 && hasID : %d\n", age > 18 && hasID);
    printf("age < 18 || hasID : %d\n", age < 18 || hasID);
    printf("!hasID            : %d\n", !hasID);

    return 0;
}

πŸ–₯️ Output:
age > 18 && hasID : 1
age < 18 || hasID : 1
!hasID             : 0

πŸ’‘ What “truthy” means in C: C treats any non-zero value as true and exactly 0 as false β€” this is why hasID (storing plain 1) works directly inside a logical expression without needing to write hasID == 1.

The NOT Operator on Its Own

#include <stdio.h>

int main() {
    int isRaining = 0;
    int isWeekend = 1;

    printf("!isRaining        : %d\n", !isRaining);
    printf("!isWeekend        : %d\n", !isWeekend);
    printf("!(5 > 3)          : %d\n", !(5 > 3));
    printf("!!isRaining       : %d\n", !!isRaining);

    return 0;
}

πŸ–₯️ Output:
!isRaining : 1
!isWeekend : 0
!(5 > 3) : 0
!!isRaining : 0

! simply flips a value’s truthiness: applied to 0 it gives 1, applied to any non-zero value it gives 0. The last line shows a trick worth recognizing: !!x is a common way to “normalize” any value down to a clean 0 or 1, regardless of what non-zero number x originally held.

Combining Three Conditions

#include <stdio.h>

int main() {
    int age = 25;
    int hasTicket = 1;
    int isBanned = 0;

    int canEnter = (age >= 18) && hasTicket && !isBanned;
    printf("Can enter: %d\n", canEnter);

    return 0;
}

πŸ–₯️ Output:
Can enter: 1

Logical operators chain naturally β€” there’s no limit to how many conditions you can combine with && and || in a single expression. Here, all three conditions must hold for canEnter to be 1: the age check, having a ticket, and not being banned. Read a chain like this left to right, exactly like a checklist that has to pass every item.

Short-Circuit Evaluation

C’s logical operators have a crucial efficiency behavior: they stop evaluating the moment the final answer is already certain.

#include <stdio.h>

int checkID() {
    printf("(checking ID...)\n");
    return 1;
}

int main() {
    int isRegistered = 0;

    int accessGranted = isRegistered && checkID();
    printf("Access granted: %d\n", accessGranted);

    return 0;
}

πŸ–₯️ Output:
Access granted: 0

Notice "(checking ID...)" never printed. Since isRegistered is already 0 (false), C knows the entire && expression must be false no matter what checkID() would return β€” so it skips calling that function entirely. This isn’t just an optimization detail to memorize; it’s routinely used on purpose in decisions built later in this course, such as checking ptr != NULL && ptr->value > 0, where the second check would be unsafe to even attempt if the first one fails.

Short-Circuiting with OR

#include <stdio.h>

int checkBackupServer() {
    printf("(pinging backup server...)\n");
    return 1;
}

int main() {
    int primaryServerUp = 1;

    int serverAvailable = primaryServerUp || checkBackupServer();
    printf("Server available: %d\n", serverAvailable);

    return 0;
}

πŸ–₯️ Output:
Server available: 1

"(pinging backup server...)" never printed here either β€” the mirror image of the && example above. Since primaryServerUp is already 1 (true), C knows the entire || expression must be true no matter what checkBackupServer() would return, so it never bothers calling it. This is exactly the pattern behind lazily expensive fallback checks: only pay the cost of checking the backup if the primary has already failed.

❓ The Conditional (Ternary) Operator

C has exactly one operator that takes three operands: condition ? value_if_true : value_if_false. It’s a compact way to write a simple if-else that produces a value.

#include <stdio.h>

int main() {
    int marks = 78;

    char *result = marks >= 40 ? "Pass" : "Fail";
    printf("Result: %s\n", result);

    return 0;
}

πŸ–₯️ Output:
Result: Pass

The ternary operator lets you compute a value β€” here, the string "Pass" or "Fail" β€” directly as part of a larger expression. It’s especially common directly inside a printf call, since it produces a value rather than running a statement block, which makes it easy to slot into places a full branching statement (covered in the next post) couldn’t reach.

⚠️ Don’t nest ternaries for readability’s sake: x > 0 ? "positive" : x < 0 ? "negative" : "zero" technically works, but is noticeably harder to read at a glance than the equivalent chained if-else-if. Save the ternary operator for genuinely simple, single-condition choices.

Finding the Larger of Two Numbers

#include <stdio.h>

int main() {
    int a = 42, b = 17;

    int larger = (a > b) ? a : b;
    printf("Larger value: %d\n", larger);

    return 0;
}

πŸ–₯️ Output:
Larger value: 42

This is one of the most common uses of the ternary operator: picking between two values (not just two strings) based on a single comparison. Unlike printf("Result: %s\n", ...) from the previous example, here the ternary’s result is stored in a variable first β€” both styles are equally valid, depending on whether you need the value again later.

Using Ternary for Rounding Direction

#include <stdio.h>

int main() {
    int total = 47;
    int people = 5;

    int remainder = total % people;
    int roundedShare = (remainder > 0) ? (total / people) + 1 : total / people;

    printf("Each person gets at least: %d\n", roundedShare);

    return 0;
}

πŸ–₯️ Output:
Each person gets at least: 10

Here the ternary operator decides whether to round the division result up by one. 47 / 5 is 9 with a remainder of 2 β€” since that remainder is greater than 0, the ternary adds 1, giving 10 β€” enough that everyone gets at least an equal share, with a bit left over for whoever’s counting.

🧩 Compound Logical Expressions: Predicting Output

This lecture’s core skill is reading a compound expression and correctly predicting what it evaluates to β€” exactly like the leap year check from the Algorithms post, now expressed as a single line instead of three separate steps.

#include <stdio.h>

int main() {
    int year = 2024;

    int isLeap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);

    printf("isLeap = %d\n", isLeap);

    return 0;
}

πŸ–₯️ Output:
isLeap = 1

Tracing it by hand: year % 4 == 0 β†’ 2024 % 4 = 0 β†’ trueyear % 100 != 0 β†’ 2024 % 100 = 24 β†’ true. Both sides of the && are true, so the left half of the || is already true β€” meaning C short-circuits and never even checks year % 400 == 0. The entire expression evaluates to 1.

Compare this single-line version to the three-diamond flowchart from the “Introduction to Flowcharts” post β€” it’s the exact same logic, just expressed as one compound Boolean expression instead of a chain of separate decisions. Being able to translate fluently between these two forms is precisely what this lecture is building toward.

A Second Trace: Divisible by 3 and 5

#include <stdio.h>

int main() {
    int n = 15;

    int divisibleByBoth = (n % 3 == 0) && (n % 5 == 0);
    printf("Divisible by both: %d\n", divisibleByBoth);

    n = 9;
    divisibleByBoth = (n % 3 == 0) && (n % 5 == 0);
    printf("Divisible by both: %d\n", divisibleByBoth);

    return 0;
}

πŸ–₯️ Output:
Divisible by both: 1
Divisible by both: 0

Tracing the second case by hand: with n = 9n % 3 == 0 β†’ 9 % 3 = 0 β†’ true, but n % 5 == 0 β†’ 9 % 5 = 4 β†’ false. Since && requires both sides to be true, the whole expression collapses to 0 the moment the second condition fails. This exact compound condition β€” checking divisibility by two different numbers β€” is the core logic behind the classic “FizzBuzz” programming exercise you may encounter later in this course.

πŸŽ“ Worked Activity: An Eligibility Checker

Let’s combine relational and logical operators into a realistic decision-based problem β€” checking scholarship eligibility based on two independent conditions.

#include <stdio.h>

int main() {
    float percentage;
    int familyIncome;

    printf("Enter percentage marks: ");
    scanf("%f", &percentage);

    printf("Enter family income (in thousands): ");
    scanf("%d", &familyIncome);

    int isEligible = (percentage >= 75.0) && (familyIncome <= 500);

    printf("\nEligible for scholarship: %s\n", isEligible ? "Yes" : "No");

    return 0;
}

πŸ–₯️ Sample run:
Enter percentage marks: 82
Enter family income (in thousands): 320

Eligible for scholarship: Yes

Both conditions β€” good enough marks and low enough income β€” must hold at the same time, which is exactly what && enforces. The ternary operator in the final printf then converts the raw 0/1 result into a readable "Yes"/"No" for the user, instead of printing a bare number.

🚫 Common Mistakes Beginners Make

MistakeWhy it happens
Writing if (a = 5) instead of if (a == 5)Compiles without error, but assigns instead of comparing β€” the condition is always true
Writing if (0 < x < 10) to check a rangeEvaluates left to right: (0 < x) first gives 0 or 1, which is then compared against 10 β€” always true. Use x > 0 && x < 10 instead.
Assuming C has a real bool type by defaultStandard C represents true/false as plain ints (0 or 1) unless you explicitly #include <stdbool.h>
Relying on a function call inside && or || always runningShort-circuit evaluation may skip it entirely β€” don’t put side effects you depend on inside the second operand
Nesting several ternary operators for complex logicTechnically legal, but hurts readability fast β€” prefer if-else-if once there’s more than one condition

πŸŽ“ Practice Exercises

Exercise 1: Predict the Output

int x = 5, y = 10, z = 15;
printf("%d\n", (x < y) && (y < z));
printf("%d\n", (x > y) || (y < z));

(Answer: 1 then 1 β€” both comparisons in the first line are true; in the second, the left side is false but the right side is true, so || still gives true.)

Exercise 2: Spot the Bug

int score = 85;
int isPerfect = (score = 100);
printf("%d\n", isPerfect);

(Answer: it prints 100, and β€” worse β€” score has now silently changed to 100 too. score = 100 is an assignment, not a comparison; it sets score to 100 and the expression evaluates to that assigned value. Fix: use score == 100 to compare instead of assign.)

Exercise 3: Build It Yourself

Write a program that reads three integers and uses only relational and logical operators (no nested if-statements) to print whether all three are equal, using a single compound expression.

❓ Frequently Asked Questions

Q: Does C have a real true and false keyword?
Not in classic C β€” you’d use 1 and 0 directly, or #include <stdbool.h> (available since C99) to get booltrue, and false as more readable aliases for the same underlying integers.

Q: What does && or || actually return β€” is it always exactly 1 or 0?
Yes β€” unlike some other languages, C’s logical operators always produce exactly 1 or 0, even though the individual operands being combined might be any non-zero “truthy” value.

Q: Why does short-circuit evaluation matter beyond just performance?
It’s often used for safety, not just speed β€” checking a condition that must be true before it’s safe to check the next one, like verifying an array index is in bounds before accessing that index in the same expression.

Q: Is there a difference between &&/|| and the single-character &/|?
Yes, and mixing them up is a real bug source: & and | are bitwise operators that work on individual binary digits, not logical operators β€” they don’t short-circuit and behave completely differently. We’ll cover bitwise operators in a dedicated post later in this course.

βœ… Key Takeaways

  • Relational operatorsΒ (== != > < >= <=) compare two values and produceΒ 1Β (true) orΒ 0Β (false) β€” plain integers, since C has no dedicated boolean type by default.
  • Logical operatorsΒ (&& || !) combine multiple conditions β€” and any non-zero value counts as “true” when used inside one.
  • Short-circuit evaluationΒ means C stops checking a compound condition the moment the final answer is already certain β€” skipping the remaining operand entirely.
  • TheΒ ternary operatorΒ (?:) is a compact substitute for a simple if-else that produces a value β€” best kept to single, simple conditions.
  • The single most common bug in this entire area isΒ writingΒ =Β when you meantΒ ==Β β€” it compiles, but silently changes your program’s behavior.

πŸš€ Next up: With every kind of operator now covered, we’ll move into control structures β€” how ifelse if, and switch actually branch a program’s flow in real C syntax, building directly on the decision-making logic from this post.