Senior 3 min · March 17, 2026

Python if-elif-else – The Falsy Integer $50k Bug

if amount: evaluates to False when amount is 0, causing a $50k billing bug.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Conditional branching in Python: if, elif, else — indentation defines blocks, no braces.
  • elif is Python's "else if" — writing else if creates a nested block, not a chain.
  • Truthy/falsy: empty containers, None, 0, 0.0, and False are falsy; everything else is truthy.
  • Ternary expression: value_if_true if condition else value_if_false (note: condition in the middle).
  • Performance: Python short-circuits boolean operators — order conditions wisely to skip expensive checks.
  • Production trap: falsy 0 or empty string can trigger else when you meant to handle them as valid values.
Plain-English First

Conditional statements let your code make decisions. Think of it like a choose-your-own-adventure book: if this is true, go here; else if that is true, go there; else do this default. Python uses indentations instead of brackets to show which blocks belong together.

Conditional logic is the first place where programs become interesting — where they make decisions. Python's if-elif-else syntax is clean and readable, but there are a few specifics that trip up people coming from other languages.

The main things to get right: truthy and falsy values (Python is more permissive than most languages about what counts as True), the elif keyword (not else if), and the ternary expression syntax which is the reverse of most languages.

Here's the thing most tutorials skip: the subtle production bugs that come from treating falsy values as errors. You'll write if user_input: and it'll silently skip valid input like 0 or an empty string. In real systems, that kind of shortcut costs you hours of debugging.

Basic if-elif-else

The simplest branching constructs. Python executes top-down: the first condition that evaluates to True triggers its block, then the rest of the chain is skipped. Indentation must be consistent — 4 spaces per PEP 8.

PYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Package: io.thecodeforge.python.control_flow

score = 73

if score >= 90:
    grade = 'A'
elif score >= 80:
    grade = 'B'
elif score >= 70:
    grade = 'C'
elif score >= 60:
    grade = 'D'
else:
    grade = 'F'

print(f'Score {score} → Grade {grade}')  # Score 73 → Grade C

# Conditions are checked top to bottom — first True wins
# Once a branch executes, the rest are skipped
Indentation Consistency
Mixing tabs and spaces silently breaks your code. Most editors convert tabs to spaces, but if your team standardises on tabs, never use spaces inside the same file.
Production Insight
Order matters. Put the most specific or most expensive condition first only if it's likely True.
Real bug: a health-check loop checked if response.timeout: before if response.ok: — the timeout condition never fired because response.ok was True even for timeouts in some libraries.
Rule: check most restrictive conditions first, and avoid relying on side effects in condition evaluation.
Key Takeaway
elif replaces else if in Python.
Indentation defines block boundaries.
First True branch wins — order your conditions with intent.
When to Use if-elif-else vs Other Constructs
IfSingle condition, else optional
UseUse if-else (the else is optional).
IfMultiple mutually exclusive conditions based on same variable
UseUse elif chain. For many options, consider a dict mapping.
IfNeed to match on structure or type, not just equality?
UseUse match-case (Python 3.10+). Intended for pattern matching.

Truthy and Falsy Values

Python evaluates any object in a boolean context. Knowing what is falsy saves you from writing verbose comparisons. But beware: the convenience of if x: can hide logical errors when falsy values are valid inputs.

PYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Falsy values — evaluate to False in a boolean context
falsy_examples = [
    False, 0, 0.0, 0j,       # false booleans and zeros
    '', [], {}, set(), (),    # empty sequences and collections
    None,                     # the None singleton
]

for val in falsy_examples:
    if not val:
        print(f'{repr(val):10} is falsy')

# In practice:
name = input('Enter name: ')  # imagine user entered ''
if name:  # cleaner than: if name != ''
    print(f'Hello, {name}')
else:
    print('No name provided')

# Common pitfall: 0 is falsy
count = 0
if count:  # This is False — but count is a valid value!
    print('Has items')
# Better:
if count is not None:  # only check for missing, not zero
Don't Rely on Truthiness for Primitives
Using if x: to check if a number or string is "present" will reject legitimate zeros and empty strings. Always consider the domain: if zero is a valid value, use if x is not None or if x != 0.
Production Insight
The falsy trap is the #1 cause of silent logic failures in Python production code.
Senior engineers never write if user_input: without confirming that empty/falsy is truly invalid for that field.
Rule: define explicit sentinel values (like UNSET = object()) instead of relying on truthiness.
Key Takeaway
Falsy: False, 0, 0.0, '', [], {}, set(), (), None.
Truthy: everything else.
But use explicit checks when zero or empty is a valid value.

Ternary Expression

Python's ternary is the reverse of most languages — condition comes in the middle, not at the start. It's a one-liner, but readability plummets if you chain them. Reserve it for single, obvious conditions.

PYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
age = 20

# Ternary: value_if_true if condition else value_if_false
status = 'adult' if age >= 18 else 'minor'
print(status)  # adult

# Common uses
temperature = -5
description = 'freezing' if temperature < 0 else 'warm' if temperature < 20 else 'hot'
print(description)  # freezing

# Conditional assignment (also works, but less expressive)
max_val = a if a > b else b  # same as max(a, b)

# Do NOT use for complex logic — readability drops fast
# This is too much for one line:
# result = 'a' if x > 0 else 'b' if x < 0 else 'c' if x == 0 else 'd'
When to Avoid Ternary
If the expression doesn't fit comfortably on one line (or needs nested ternaries), use a standard if-elif-else. Code is read more often than it's written.
Production Insight
Nested ternaries are a favourite for causing confusion in code reviews. They also make debugging harder — you can't put a breakpoint on part of a ternary.
Rule: limit ternaries to simple True/False assignments with no side effects.
Key Takeaway
Syntax: value if condition else other_value.
Condition is in the middle, not the front.
Never nest ternaries — use regular if blocks.

match-case — Python 3.10+

Python 3.10 added structural pattern matching. It is more powerful than a chain of elif — it can match on structure, not just equality. Use it when you'd otherwise write a long elif chain on a single value or when you need to destructure nested data.

PYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def http_status(code: int) -> str:
    match code:
        case 200: return 'OK'
        case 201: return 'Created'
        case 400: return 'Bad Request'
        case 401 | 403: return 'Auth error'  # multiple values with |
        case 404: return 'Not Found'
        case 500: return 'Internal Server Error'
        case _: return f'Unknown status: {code}'  # default

print(http_status(200))  # OK
print(http_status(403))  # Auth error
print(http_status(999))  # Unknown status: 999

# Structural matching on tuples
def describe_point(point):
    match point:
        case (0, 0): return 'Origin'
        case (x, 0): return f'On x-axis at {x}'
        case (0, y): return f'On y-axis at {y}'
        case (x, y): return f'Point at ({x}, {y})'

print(describe_point((0, 0)))   # Origin
print(describe_point((3, 0)))   # On x-axis at 3
print(describe_point((2, 5)))   # Point at (2, 5)
match-case is not switch()
  • Matches on type, value, and structure simultaneously.
  • Supports guards: case x if x > 0: adds extra conditions.
  • Wildcard _ is the default — must come last.
  • Can match against class instances and custom objects.
Production Insight
match-case is fast but not as fast as a lookup dict for simple equality checks.
If you're matching against a fixed set of constants (like HTTP status codes), a dict is both faster and more readable.
Use match-case when the pattern is complex: matching on tuples, lists, or objects with structure.
Key Takeaway
match-case is for STRUCTURAL pattern matching, not just value switching.
Combine with guards for conditional logic.
For pure value-to-value mapping, a dict is simpler and faster.

Nested Conditionals and Short-Circuit Evaluation

Python evaluates boolean expressions lazily: and stops at the first False, or at the first True. This helps you avoid NoneType errors and expensive function calls. But it also means the order of your conditions matters both for logic and performance.

PYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Safe access with short-circuit
data = None
if data and data.get('key'):
    print(data['key'])  # never runs, because data is falsy

# Performance: put cheap checks first
def expensive_check():
    # imagine hitting a database
    return True

x = 5
if x > 0 and expensive_check():
    print('Both true')
# expensive_check() only runs if x > 0 is True

# Nested conditionals — prefer flattening
# This:
if condition_a:
    if condition_b:
        do_something()
elif condition_c:
    if condition_d:
        do_other()

# Is better written as:
if condition_a and condition_b:
    do_something()
elif condition_c and condition_d:
    do_other()
Flattening Nested ifs
Deep nesting hurts readability. Use and/or to collapse simple nests. For more complex cases, extract conditions into named variables or helper functions.
Production Insight
Short-circuit is your friend — but only if you understand it.
Real bug: a logging system tried to access log['level'] before checking log is not None. The and guard log and log['level'] failed because a valid empty dict {} is falsy.
Rule: when the left operand can be a legitimate falsy value (empty dict, zero), use a more explicit guard like log is not None and log.get('level').
Key Takeaway
and short-circuits on False, or short-circuits on True.
Put cheap conditions first to skip expensive checks.
Avoid deep nesting — use boolean operators or early returns.
● Production incidentPOST-MORTEMseverity: high

The $50k Billing Bug Caused by Falsy Integers

Symptom
Customers reported receiving discount coupons but never seeing them applied. Logs showed the discount logic was never reached for amounts of $0.00.
Assumption
The team assumed if amount: was safe because "zero means no transaction" — but discount coupons use $0.00 as a valid value.
Root cause
if amount: evaluates to False when amount is 0 (int or float). The discount application code was in the else branch, so it never ran for zero-dollar coupons.
Fix
Changed the condition to if amount is not None: to distinguish between "no amount" and "amount is zero". Added explicit check for None before processing.
Key lesson
  • Never use truthiness to check presence of numeric or string values that could legitimately be zero or empty.
  • Prefer explicit comparisons: if x is not None or if x != 0 over if x.
  • Add unit tests that explicitly test falsy boundary values (0, 0.0, '', [], etc.).
Production debug guideSymptom → Action guide for common if-elif-else failures4 entries
Symptom · 01
Expected branch never executes, no error raised
Fix
Check for falsy short-circuit: if the condition involves a numeric/string variable, verify it's not 0 or empty. Add print(repr(condition)) before the if.
Symptom · 02
Multiple conditions fire unexpectedly
Fix
Check for else if instead of elif. else if creates a nested block — the second if runs regardless of the first condition's outcome.
Symptom · 03
Ternary expression returns wrong value
Fix
Remember the order: true_val if condition else false_val. Use parentheses to group complex conditions. Break into full if-else if still unclear.
Symptom · 04
match-case block doesn't catch a value that should match
Fix
Make sure you didn't forget the wildcard case _: at the end. For structural matching, ensure the value is the exact type expected (e.g., tuple vs list).
Conditional Constructs Compared
ConstructSyntaxBest ForReadabilityPerformance
if-elif-elseif cond: ... elif cond2: ... else: ...Multiple exclusive conditionsHigh (standard)Fast (condition order matters)
Ternaryval if cond else otherSimple True/False assignmentMedium (good for one-liners)Same as if-else
match-casematch val: case pat: ... case _:Structural pattern matching, complex type-based dispatchHigh for structured dataSlightly slower than dict lookup for pure value matching

Key takeaways

1
Python uses elif, not else if
else if creates a nested else block, which is valid but rarely what you mean.
2
Indentation defines the block
be consistent with 4 spaces (PEP 8 standard).
3
Python evaluates many things as falsy
None, 0, '', [], {} — learn the list to write cleaner conditions.
4
Ternary syntax
value_if_true if condition else value_if_false.
5
Python 3.10+ match-case is not just a switch statement
it does structural pattern matching.
6
Short-circuit evaluation is powerful but can hide bugs when falsy values are legitimate inputs.

Common mistakes to avoid

3 patterns
×

Using `else if` instead of `elif`

Symptom
Code executes unexpected path: the else block runs and then the nested if runs, causing two branches to execute.
Fix
Replace else if with elif. Understand that else if creates a new nested if statement inside the else block.
×

Relying on truthiness for values that can be zero or empty

Symptom
A valid score of 0 gets treated as missing. A form with an empty string for a middle name field gets rejected.
Fix
Use explicit checks: if x is not None instead of if x when zero or empty are valid values. Document the expected behaviour.
×

Ordering conditions from least to most specific

Symptom
A generic condition (like score > 50) catches values before a more specific one (score > 80), so the specific branch never fires.
Fix
Always order the narrowest (most specific) conditions first. You can also use elif with and to combine conditions.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What values are considered falsy in Python?
Q02SENIOR
What is the difference between elif and a nested if-else?
Q03JUNIOR
What is the syntax for a ternary expression in Python?
Q04SENIOR
Explain how short-circuit evaluation affects conditional performance in ...
Q01 of 04JUNIOR

What values are considered falsy in Python?

ANSWER
Falsy values are: False, None, zero numeric types (0, 0.0, 0j), empty strings (''), and empty collections ([], {}, set(), (), range(0)). Custom classes can implement __bool__ or __len__ to define their truthiness.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
What is the difference between elif and else if in Python?
02
Can a Python if statement have no else?
03
Is Python's match-case the same as a switch statement?
04
How can I safely check if a variable is None without treating zero as None?
🔥

That's Control Flow. Mark it forged?

3 min read · try the examples if you haven't

Previous
Python enumerate(): Loop with Index and Value
1 / 7 · Control Flow
Next
for Loop in Python