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.
| Operator | Meaning | Example | Result |
|---|---|---|---|
== | Equal to | 5 == 5 | 1 |
!= | Not equal to | 5 != 3 | 1 |
> | Greater than | 7 > 2 | 1 |
< | Less than | 7 < 2 | 0 |
>= | Greater than or equal to | 5 >= 5 | 1 |
<= | Less than or equal to | 5 <= 4 | 0 |
#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 β true. year % 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 = 9, n % 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
| Mistake | Why 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 range | Evaluates 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 default | Standard C represents true/false as plain ints (0 or 1) unless you explicitly #include <stdbool.h> |
Relying on a function call inside && or || always running | Short-circuit evaluation may skip it entirely β don’t put side effects you depend on inside the second operand |
| Nesting several ternary operators for complex logic | Technically 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 bool, true, 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
if,else if, andswitchactually branch a program’s flow in real C syntax, building directly on the decision-making logic from this post.