Booleans, Comparison and Logical Operators

Booleans and comparison logic are the foundation of every conditional in your FastAPI application โ€” checking whether a user is authenticated, whether a post is published, whether a request value is within an allowed range. Python’s approach to booleans is more expressive than most languages: logical operators are English words (and, or, not), the language distinguishes between equality (==) and identity (is), and every Python object has a truth value that can be used directly in conditions. Understanding short-circuit evaluation and identity vs equality is especially important when working with None values in SQLAlchemy database results.

Boolean Basics

# Python booleans: True and False (capitalised)
is_active    = True
is_deleted   = False

# bool is a subclass of int in Python
print(int(True))    # 1
print(int(False))   # 0
print(True + True)  # 2 โ€” valid but unusual
print(True * 5)     # 5

# Useful: count True values in a list
results = [True, False, True, True, False]
print(sum(results))   # 3 โ€” counts True values
Note: Python’s boolean operators and, or, not are spelled out as English words โ€” they are not &&, ||, ! as in JavaScript. This is one of the most common syntax errors for developers transitioning from JavaScript. The words feel natural once you get used to them: if is_admin and is_active: reads almost like English.
Tip: Python’s or operator can replace simple ternary expressions for default values โ€” name = user_input or "Guest" returns user_input if it is truthy, otherwise "Guest". This pattern is common in FastAPI for providing default values: title = request.title or "Untitled". However, be careful when the value 0 or an empty string is a valid input โ€” falsy values would incorrectly trigger the default.
Warning: Use is to compare with None, never ==. value is None checks identity (is this exactly the None object?), while value == None can be overridden by custom __eq__ methods. SQLAlchemy model attributes and FastAPI optional parameters return None when unset โ€” always check them with is None or is not None.

Comparison Operators

x, y = 10, 20

x == y    # False  โ€” equal value
x != y    # True   โ€” not equal
x < y     # True   โ€” less than
x > y     # False  โ€” greater than
x <= y    # True   โ€” less than or equal
x >= y    # False  โ€” greater than or equal

# Python allows chained comparisons (unique to Python)
age = 25
18 <= age <= 65    # True โ€” equivalent to (18 <= age) and (age <= 65)

# Range checking (elegant Python idiom)
score = 87
if 0 <= score <= 100:
    print("Valid score")

# String comparison โ€” lexicographic (alphabetical)
"apple" < "banana"    # True
"alice" == "Alice"    # False (case-sensitive)
"abc" < "abd"         # True (compares char by char)

Logical Operators โ€” and, or, not

# Python uses English words, not symbols
# and โ†’ both must be True
# or  โ†’ at least one must be True
# not โ†’ negates the boolean value

is_admin  = True
is_active = False

is_admin and is_active    # False (both must be True)
is_admin or  is_active    # True  (at least one is True)
not is_admin              # False (negates True)
not is_active             # True  (negates False)

# Combining conditions
age = 25
has_id = True

if age >= 18 and has_id:
    print("Entry allowed")

# not with parentheses for clarity
if not (age < 18 or not has_id):
    print("Also entry allowed")

# De Morgan's laws โ€” equivalent forms:
# not (A and B) == (not A) or (not B)
# not (A or B)  == (not A) and (not B)

Short-Circuit Evaluation

# Short-circuit: Python stops evaluating as soon as result is certain

# 'and' short-circuits on first False
# 'or'  short-circuits on first True

def expensive():
    print("Expensive called!")
    return True

False and expensive()   # "Expensive called!" is NOT printed
True  or  expensive()   # "Expensive called!" is NOT printed

# Practical use: guard against None before accessing attribute
user = None
if user is not None and user.is_active:   # safe โ€” doesn't crash if user is None
    print("Active user")

# 'or' for default values (short-circuit)
name = "" or "Guest"       # "Guest" โ€” empty string is falsy
name = "Alice" or "Guest"  # "Alice" โ€” non-empty is truthy, stops here

# 'and' to return value only if condition is met
result = is_admin and "Admin Panel"   # "Admin Panel" if admin, else False

Identity vs Equality โ€” is vs ==

# == checks VALUE equality
# is checks IDENTITY (same object in memory)

a = [1, 2, 3]
b = [1, 2, 3]
c = a

a == b    # True  โ€” same values
a is b    # False โ€” different objects in memory
a is c    # True  โ€” c points to the SAME object as a

# ALWAYS use 'is' for None checks
value = None
value is None     # True  โ€” correct
value == None     # True  โ€” works but poor style, can be overridden

# ALWAYS use 'is' for True/False identity checks (rare)
# value is True     # checks if it's the exact True object
# value == True     # checks if value equals 1 (since bool is int)

# Python caches small integers โ€” don't rely on 'is' for numbers
x = 256; y = 256; x is y   # True (cached)
x = 257; y = 257; x is y   # False (not cached โ€” implementation detail!)

# Rule: use 'is' ONLY for None, True, and False
# Use '==' for all other value comparisons

Membership and Identity Operators

# Membership: 'in' and 'not in'
fruits = ["apple", "banana", "cherry"]
"apple"  in fruits       # True
"mango"  in fruits       # False
"mango"  not in fruits   # True

# Works on strings too
"ell" in "hello"         # True
"xyz" not in "hello"     # True

# Works on dicts (checks keys by default)
user = {"name": "Alice", "role": "admin"}
"role"  in user          # True (checks keys)
"admin" in user          # False (does not check values by default)
"admin" in user.values() # True โœ“

# FastAPI use: check if a role is permitted
ALLOWED_ROLES = {"user", "editor", "admin"}  # set โ€” O(1) lookup
def check_role(role: str) -> bool:
    return role in ALLOWED_ROLES

Common Mistakes

Mistake 1 โ€” Using JavaScript logical operators

โŒ Wrong โ€” JavaScript-style operators:

if is_admin && is_active:   # SyntaxError
if !is_deleted:             # SyntaxError

โœ… Correct โ€” Python uses English words:

if is_admin and is_active:  # โœ“
if not is_deleted:          # โœ“

Mistake 2 โ€” Using == to check for None

โŒ Wrong โ€” equality check for None:

if result == None:     # works but poor style

โœ… Correct โ€” identity check for None:

if result is None:     # โœ“ Pythonic, safe with custom __eq__
if result is not None: # โœ“ for the positive case

Mistake 3 โ€” Relying on truthiness when 0 or "" are valid values

โŒ Wrong โ€” 0 and empty string are falsy, causes bugs:

count = 0
label = ""
name = count or "no items"   # "no items" โ€” but 0 is a valid count!
title = label or "untitled"  # "untitled" โ€” but "" might be intentional

โœ… Correct โ€” check explicitly with is None:

name  = "no items" if count is None else count   # โœ“ 0 is kept
title = "untitled" if label is None else label   # โœ“ "" is kept

Quick Reference

Operator Python JS Equivalent Example
Logical AND and && a and b
Logical OR or || a or b
Logical NOT not ! not a
Equal value == === a == b
Not equal != !== a != b
Identity is n/a x is None
Not identity is not n/a x is not None
Membership in n/a "a" in list
Not membership not in n/a "a" not in list
Chained compare 0 <= x <= 100 n/a (use &&) Range check

🧠 Test Yourself

A FastAPI endpoint returns a post object from the database. You want to check that the post exists AND is published. The post may be None if not found. Which condition is safe?