Short Circuiting in C
Need a fast revision version? See the C Programming Cheat Sheet →Table of Contents
- What is Short-Circuit Evaluation?
- Short-Circuiting with Logical AND (&&)
- Short-Circuiting with Logical OR (||)
- Why Short-Circuiting Matters
- Common Patterns & Idioms
- Interaction with Side Effects
- Short-Circuiting vs Bitwise Operators
- Nested Logical Expressions
- Common Mistakes
- Practice MCQs
- Key Takeaways
1. What is Short-Circuit Evaluation?
Short-circuit evaluation is a behavior of the logical operators && (Logical AND) and || (Logical OR) where the second operand is evaluated only if necessary to determine the final result.
In other words, the compiler “short-circuits” the evaluation as soon as the outcome is known, skipping the evaluation of the remaining expressions.
Which Operators Short-Circuit?
Only two operators in C exhibit short-circuit behavior:
| Operator | Name | Short-Circuits When… |
|---|---|---|
&& | Logical AND | First operand is false (0) |
| ` | ` |
Important: The bitwise operators
&and|do NOT short-circuit. They always evaluate both operands.
2. Short-Circuiting with Logical AND (&&)
Rule
For A && B:
- If
Aevaluates to0(false), the entire expression isfalse, soBis never evaluated. - If
Aevaluates to non-zero (true),Bmust be evaluated to determine the final result.
Truth Table
| A | B Evaluated? | A && B |
|---|---|---|
| 0 (false) | No | 0 |
| non-zero (true) | Yes | Depends on B |
Example 1: Basic Short-Circuit
printf("%d", 0 && 10 / 0);
Evaluation:
- The first operand is
0(false). - Since
0 && anythingis always0, the second operand10 / 0is never evaluated. - Output:
0
Note:
10 / 0would normally cause undefined behavior (division by zero), but because of short-circuiting, it is safely skipped.
Example 2: Both Operands Evaluated
printf("%d", 1 && 5);
Evaluation:
- The first operand is
1(true). - The second operand must be evaluated.
5is non-zero (true).- Output:
1
Example 3: Function Call Skipped
#include <stdio.h>
int fun() {
printf("Function Called\n");
return 1;
}
int main() {
printf("%d", 0 && fun());
}
Output:
0
fun() is never called because the first operand is 0.
3. Short-Circuiting with Logical OR (||)
Rule
For A || B:
- If
Aevaluates to non-zero (true), the entire expression istrue, soBis never evaluated. - If
Aevaluates to0(false),Bmust be evaluated to determine the final result.
Truth Table
| A | B Evaluated? | A || B | |:-:|:------------:|:------:| | non-zero (true) | No | 1 | | 0 (false) | Yes | Depends on B |
Example 1: Basic Short-Circuit
printf("%d", 5 || 10 / 0);
Evaluation:
- The first operand is
5(true). - Since
anything || trueis always1, the second operand10 / 0is never evaluated. - Output:
1
Example 2: Both Operands Evaluated
printf("%d", 0 || 5);
Evaluation:
- The first operand is
0(false). - The second operand must be evaluated.
5is non-zero (true).- Output:
1
Example 3: Function Call Skipped
#include <stdio.h>
int fun() {
printf("Function Called\n");
return 1;
}
int main() {
printf("%d", 1 || fun());
}
Output:
1
fun() is never called because the first operand is 1.
4. Why Short-Circuiting Matters
1. Preventing Runtime Errors
Short-circuiting is commonly used to guard against operations that would fail under certain conditions.
Guarding Division by Zero
// Without short-circuiting (dangerous):
if (b / a > 5) { } // Crashes if a == 0!
// With short-circuiting (safe):
if (a != 0 && b / a > 5) { }
If a is 0, the first condition a != 0 is false, so b / a is never evaluated.
Guarding NULL Pointer Dereference
// Without short-circuiting (dangerous):
if (ptr->data == 10) { } // Crashes if ptr is NULL!
// With short-circuiting (safe):
if (ptr != NULL && ptr->data == 10) { }
If ptr is NULL, the first condition fails, and ptr->data is never accessed.
Guarding Array Bounds
if (index >= 0 && index < size && arr[index] == target) { }
If index is out of bounds, arr[index] is never accessed.
2. Performance Optimization
Short-circuiting can avoid expensive computations when the result is already determined.
if (cheap_check() && expensive_computation()) { }
If cheap_check() returns false, expensive_computation() is skipped entirely.
3. Conditional Initialization
char *name = get_name();
if (name && strlen(name) > 0) {
printf("Name: %s\n", name);
}
If name is NULL, strlen(name) is never called (which would crash).
5. Common Patterns & Idioms
Pattern 1: Safe Division
if (divisor != 0 && dividend / divisor > threshold) { }
Pattern 2: Safe Pointer Access
if (ptr && ptr->next && ptr->next->value > 0) { }
Pattern 3: Safe String Operations
if (str && *str && isalpha(*str)) { }
Checks: (1) pointer not NULL, (2) string not empty, (3) first char is alphabetic.
Pattern 4: Default Value Assignment
int value = (ptr && ptr->valid) ? ptr->data : DEFAULT_VALUE;
Pattern 5: Early Exit in Functions
int process(int *arr, int size) {
if (!arr || size <= 0) return -1; // Guard clause
// ... process array safely ...
}
Pattern 6: Feature Detection
if (supports_feature() && enable_feature()) { }
Only attempt to enable a feature if the system supports it.
6. Interaction with Side Effects
Short-circuiting becomes tricky when the skipped operand has side effects — operations that modify state (variable assignments, function calls, I/O, increment/decrement).
Example 1: Skipped Increment
int i = 0;
printf("%d", 0 && (i++));
printf("%d", i);
Output:
0
0
i++ is never executed because the first operand is 0.
Example 2: Post-increment with OR
int i = 0;
printf("%d", i++ || 5);
printf("%d", i);
Evaluation:
i++returns the original value0, then incrementsito1.- Since the first operand was
0, the second operand5must be evaluated. - Output:
1(from0 || 5), then1(value ofi).
Example 3: Pre-increment with AND
int i = 0;
printf("%d", ++i && 5);
printf("%d", i);
Evaluation:
++iincrementsito1and returns1.- Since the first operand is
1(true), the second operand5is evaluated. 1 && 5=1.- Output:
1, then1.
Example 4: Complex Side Effects
int i = 5;
printf("%d", i-- && 0);
printf("%d", i);
Evaluation:
i--returns5, then decrementsito4.- First operand is
5(true), so second operand0is evaluated. 5 && 0=0.- Output:
0, then4.
Rule of Thumb
Never rely on side effects in the second operand of && or ||. The side effect may or may not occur depending on the first operand, making code unpredictable and hard to debug.
Bad Practice:
if (condition && (x++)) { } // x may or may not increment!
Good Practice:
if (condition) {
x++;
}
7. Short-Circuiting vs Bitwise Operators
This is one of the most common sources of confusion in C.
Comparison Table
| Property | && / || (Logical) | & / | (Bitwise) |
|----------|:---------------------:|:-------------------:|
| Evaluates both operands? | No (short-circuits) | Yes (always) |
| Operates on | Whole value (true/false) | Each bit individually |
| Return value | Always 0 or 1 | Integer with modified bits |
| Use case | Boolean logic, conditions | Bit manipulation, flags |
| Side effects in 2nd operand | May be skipped | Always executed |
Example: Critical Difference
int fun() {
printf("Called\n");
return 1;
}
// Logical AND -- short-circuits
printf("%d", 0 && fun()); // Output: 0 (fun() NOT called)
// Bitwise AND -- does NOT short-circuit
printf("%d", 0 & fun()); // Output: 0 (fun() IS called, prints "Called")
When to Use Which?
- Use
&&and||for boolean conditions and control flow. - Use
&and|for bit manipulation, flag operations, and when you need both operands evaluated.
8. Nested Logical Expressions
When multiple logical operators are combined, short-circuiting applies at each step.
Example: Chained AND
printf("%d", 0 && 10 && 20 / 0);
Evaluation:
0 && 10=0(short-circuits,10not evaluated)0 && (20 / 0)=0(short-circuits,20 / 0not evaluated)- Output:
0
Example: Chained OR
printf("%d", 5 || 0 || 10 / 0);
Evaluation:
5 || 0=1(short-circuits,0not evaluated… wait!)
Actually, let me correct: || is left-associative, so:
5 || 0— first operand is5(true), so result is1,0is skipped.1 || (10 / 0)— first operand is1(true), so10 / 0is skipped.- Output:
1
Example: Mixed AND and OR
printf("%d", 1 || 0 && 0);
Evaluation:
&&has higher precedence than||.- Evaluate
0 && 0first =0. - Then
1 || 0=1. - Output:
1
Note: The
0 && 0is fully evaluated because both are needed for the&&. But if it were0 && (10/0), the division would be skipped.
Example: Complex Expression
printf("%d", 0 || 2 && 0);
Evaluation:
&&has higher precedence: evaluate2 && 0=0.- Then
0 || 0=0. - Output:
0
9. Common Mistakes
Mistake 1: Assuming both operands are always evaluated
int x = 0;
if (x != 0 && 10 / x > 5) { } // Safe
// But if you accidentally use & instead of &&:
if (x != 0 & 10 / x > 5) { } // CRASH! 10/0 is evaluated
Mistake 2: Using bitwise operators for boolean logic
// Wrong: bitwise OR does not short-circuit
if (ptr == NULL | ptr->value == 0) { } // CRASH if ptr is NULL!
// Correct: logical OR short-circuits
if (ptr == NULL || ptr->value == 0) { } // Safe
Mistake 3: Relying on side effects in short-circuited expressions
int count = 0;
if (condition && (count++)) { }
// count may or may not be incremented -- unpredictable!
Mistake 4: Confusing precedence
// What does this evaluate to?
int result = 1 || 0 && 0;
&& has higher precedence than ||, so this is 1 || (0 && 0) = 1 || 0 = 1.
Always use parentheses for clarity:
int result = 1 || (0 && 0);
Mistake 5: Thinking || always evaluates both operands
if (is_valid(x) || process(x)) { }
If is_valid(x) returns true, process(x) is never called. If process(x) has necessary side effects, they will be missed.
10. Practice MCQs
MCQ 1
printf("%d", 0 && 10 / 0);
A. Runtime Error
B. 0
C. 1
D. Undefined
Answer: B (0)
The first operand is 0, so 10 / 0 is never evaluated. No crash occurs.
MCQ 2
printf("%d", 5 || 10 / 0);
A. Runtime Error
B. 0
C. 1
D. Undefined
Answer: C (1)
The first operand is 5 (true), so 10 / 0 is never evaluated.
MCQ 3
int i = 0;
printf("%d", i++ && 5);
printf("%d", i);
A. 0 0
B. 0 1
C. 1 1
D. 1 0
Answer: B (0 1)
i++ returns 0 (post-increment), so the second operand 5 is skipped. But i is incremented to 1.
MCQ 4
int i = 0;
printf("%d", ++i || 0);
A. 0
B. 1
C. Runtime Error
D. Undefined
Answer: B (1)
++i makes i = 1. Since 1 is true, 0 is skipped. Result is 1.
MCQ 5
int i = 0;
printf("%d", i++ || 5);
printf("%d", i);
A. 1 1
B. 0 1
C. 1 0
D. 0 0
Answer: A (1 1)
i++ returns 0, then i becomes 1. Since first operand was 0, second operand 5 is evaluated. 0 || 5 = 1.
MCQ 6
int i = 5;
printf("%d", i-- && 0);
printf("%d", i);
A. 0 4
B. 1 5
C. 0 5
D. 1 4
Answer: A (0 4)
i-- returns 5, then i becomes 4. 5 && 0 = 0.
MCQ 7
printf("%d", 0 || 0 || 10);
A. 0
B. 1
C. 10
D. Error
Answer: B (1)
0 || 0 = 0, then 0 || 10 = 1 (since 10 is non-zero).
MCQ 8
printf("%d", 5 && 6 && 7);
A. 0
B. 1
C. 7
D. 5
Answer: B (1)
5 && 6 = 1, then 1 && 7 = 1. Logical AND always returns 0 or 1.
MCQ 9
printf("%d", 0 || 2 && 0);
A. 0
B. 1
C. 2
D. Error
Answer: A (0)
&& has higher precedence: 2 && 0 = 0, then 0 || 0 = 0.
MCQ 10
printf("%d", 1 || 0 && 0);
A. 0
B. 1
C. Runtime Error
D. Undefined
Answer: B (1)
&& first: 0 && 0 = 0, then 1 || 0 = 1.
11. Key Takeaways
- Short-circuit evaluation applies only to
&&and||. &&skips the second operand if the first operand is false (0).||skips the second operand if the first operand is true (non-zero).- Short-circuiting is commonly used to avoid division by zero, null pointer dereferencing, and out-of-bounds access.
&&and||are different from the bitwise operators&and|, which always evaluate both operands.- Never rely on side effects (increments, function calls) in the second operand of
&&or||— they may be skipped. &&has higher precedence than||, but always use parentheses for clarity in complex expressions.- Short-circuiting is not just an optimization — it is a fundamental language feature that enables safe conditional programming.
End of Notes — Continue to MCQs or Flashcards