Placement Prep

Python Logical Operators: and, or, not With Examples

Python's and, or, not keywords evaluate conditions, short-circuit expressions, and power the default-value idiom. Truthy/falsy, bitwise differences, and common bugs.

By FACE Prep Team 7 min read
python logical-operators boolean programming-basics placement-prep

Python gives you three keywords for logical control: and, or, and not. No &&. No ||. No !.

If you are coming from C, Java, or JavaScript, that absence is the first thing to adjust to. Python’s logical operators return the actual operand value rather than a forced True/False, which makes them more expressive but also more surprising when you first meet them.

The Three Keywords Python Gives You

The Python language reference defines these three Boolean operations as the complete set. Each has a simple truth-table behaviour:

and: evaluates two expressions and returns the first falsy operand, or the last operand if both are truthy.

LeftRightand result
TrueTrueTrue
TrueFalseFalse
FalseTrueFalse
FalseFalseFalse

or: returns the first truthy operand, or the last operand if both are falsy.

LeftRightor result
TrueTrueTrue
TrueFalseTrue
FalseTrueTrue
FalseFalseFalse

not: returns the boolean inverse of a single expression.

Expressionnot result
TrueFalse
FalseTrue

Quick examples, all verified against Python 3:

a, b = 10, 20

print((a < b) and (a != b))   # True — both conditions are True
print((a == b) and (a < b))   # False — first condition is False, short-circuits
print((a > b) or (a != b))    # True — second condition is True
print((a > b) or (a == b))    # False — both conditions are False
print(not (a > b))             # True — reverses the False

Truthy and Falsy: What Python Actually Tests

and, or, and not do not require boolean operands. Python evaluates any value for truthiness. The built-in bool() function shows you exactly what Python sees:

print(bool(0))      # False
print(bool(0.0))    # False
print(bool([]))     # False
print(bool({}))     # False
print(bool(set()))  # False
print(bool(''))     # False
print(bool(None))   # False

print(bool(1))      # True
print(bool(-1))     # True
print(bool([0]))    # True  — a list with one element is truthy
print(bool('  '))   # True  — a string with whitespace is truthy

The rule: empty containers, zero, and None are falsy. Everything else is truthy.

This matters because and and or return the operand itself, not its boolean wrapper:

print(5 and 3)    # 3    — both truthy, returns last operand
print(0 and 3)    # 0    — first is falsy, short-circuits and returns 0
print(5 or 3)     # 5    — first is truthy, returns it immediately
print(0 or 3)     # 3    — first is falsy, returns second operand

This is intentional. It is the foundation of Python idioms like the default-value pattern.

Short-Circuit Evaluation

Python stops evaluating as soon as the result is determined:

  • and stops at the first falsy value.
  • or stops at the first truthy value.

That behaviour is called short-circuit evaluation. The practical value is in guard chains. Suppose you have a variable that might be None:

user = None

# Without guarding:
print(user.name)           # AttributeError: 'NoneType' has no attribute 'name'

# With a guard chain:
print(user is not None and user.name)   # False — stops at the first False, no AttributeError

The second expression from the right is never reached when the left side fails. That makes and a safe guard. Order matters: the cheap, likely-to-fail check goes on the left.

The same logic applies to or for defaults:

config_port = None
port = config_port or 8080
print(port)   # 8080 — config_port is falsy, so or returns 8080

For more hands-on conditional logic, the greatest-of-three program shows how chained comparisons with and replace nested if blocks cleanly.

Practical Patterns in Real Code

The Default-Value Idiom

name = input_name or 'Anonymous'
port = os.environ.get('PORT') or 8080

Clean and idiomatic. One caveat: the idiom misfires when a valid value is falsy. If port = 0 is a legitimate setting, os.environ.get('PORT') or 8080 replaces it with 8080. Fix it with an explicit None check:

raw = os.environ.get('PORT')
port = int(raw) if raw is not None else 8080

Guard Chains in Conditions

A common pattern when validating input for a simple calculator program:

def safe_divide(a, b):
    if isinstance(b, (int, float)) and b != 0:
        return a / b
    return None

The isinstance check runs first; if it fails, Python never evaluates b != 0.

List-Comprehension Filters

numbers = [0, 1, 2, 3, 4, 5, 6]
result = [n for n in numbers if n > 0 and n % 2 == 0]
print(result)   # [2, 4, 6]

Logical operators inside comprehensions are idiomatic Python. They read naturally and perform well.

all() and any()

When you need to apply a logical test to every item in a collection:

scores = [85, 90, 78, 92]
print(all(s >= 75 for s in scores))    # True — every score passes
print(any(s >= 90 for s in scores))    # True — at least one score qualifies

all() is equivalent to chaining and across the entire iterable. any() is equivalent to chaining or. Both short-circuit: all() stops at the first False, any() stops at the first truthy value.

Checking whether a character belongs to multiple categories (digit, letter, or special character) is a natural use of or chains. The character classification program demonstrates this pattern with or across str.isupper(), str.isdigit(), and explicit range checks.

Bitwise Operators: &, |, ~, ^ Are Not Logical Operators

This is a common source of confusion for students coming from C. Python has four bitwise operators that look similar to logical ones but work at the integer-bit level:

OperatorNameExampleResult
&Bitwise AND5 & 31
|Bitwise OR5 | 37
~Bitwise NOT~5-6
^Bitwise XOR5 ^ 36

Why those numbers? In binary: 5 is 0b101, 3 is 0b011.

  • 5 & 3: 0b101 & 0b011 = 0b001 = 1
  • 5 | 3: 0b101 | 0b011 = 0b111 = 7
  • ~5: flips all bits; in two’s complement, ~n = -(n + 1), so ~5 = -6
  • 5 ^ 3: 0b101 ^ 0b011 = 0b110 = 6

On plain True/False the bitwise operators produce the same result as logical ones, because Python treats True as 1 and False as 0:

print(True & False)   # False
print(True | False)   # True

The critical difference: bitwise operators do not short-circuit. Both sides are always evaluated. This matters when the right side has a side effect or is expensive:

data = None
print(data and data['key'])    # None — short-circuits, no KeyError
print(data & data['key'])      # TypeError — both sides evaluated

Use &, |, ^ for bit manipulation and for NumPy/Pandas element-wise operations. Use and, or, not for all ordinary boolean logic.

Common Bugs and Precedence Pitfalls

Operator Precedence

Python evaluates logical operators in this order (highest to lowest):

  1. not
  2. and
  3. or

So True or False and False evaluates as True or (False and False), which is True. Add parentheses whenever you mix and and or in the same expression:

# Ambiguous — relies on precedence rules
print(True or False and False)      # True

# Explicit — no guessing required
print(True or (False and False))    # True
print((True or False) and False)    # False

The parenthesised versions are equivalent in result here, but the intent is immediately clear. For anything more complex, always parenthesise.

See the full Python operator precedence table for the complete ordering.

Chained Comparisons

Python lets you write 1 < x < 10 as a genuine three-way comparison. This is not the same as 1 < x and x < 10 in terms of syntax, but Python translates it identically under the hood:

x = 5
print(1 < x < 10)              # True — correct Python chaining
print(1 < x and x < 10)        # True — identical result

# Common bug from C-habits:
# In C: (1 < x) < 10 compares the boolean result 1 to 10
# In Python: 1 < x < 10 is a true chained comparison, works as expected

Use the chained form when checking range membership: more readable and slightly faster.

Mixing and/or Without Parentheses

# Bug: intended "either admin or (active and verified)"
# Actual: "(admin or active) and verified"
if is_admin or is_active and is_verified:
    grant_access()

# Fix:
if is_admin or (is_active and is_verified):
    grant_access()

This is one of the most common logic bugs in placement coding tests. Parenthesise whenever you chain different operators.

not Precedence Surprises

x = 5
print(not x == 5)    # False — parsed as not (x == 5)
print(not x > 3)     # False — parsed as not (x > 3) which is not True = False

not has lower precedence than comparison operators (==, !=, <, >, <=, >=). It always applies to the full comparison. So not x > 3 means not (x > 3), not (not x) > 3.

The Python basic programs collection has condition-heavy examples to practise against.

Writing LLM-Powered Logic Checks

The and/or/not patterns above appear in almost every LLM application at the prompt-routing and validation layer: checking whether a user’s message is empty or malformed, whether a model response satisfies multiple criteria at once, whether all() safety checks pass before forwarding a request.

TinkerLLM at ₹299 puts you in a live Python environment where you write those filters against real API responses rather than toy variables. If you have the and/or mechanics down from this article, TinkerLLM is the next step where logical operators move from textbook examples into deployed code.

Primary sources

Frequently asked questions

Are Python's `and` and `or` the same as `&&` and `||` in C or Java?

No. Python uses the keywords `and`, `or`, and `not`. The symbols `&&`, `||`, and `!` are not valid Python syntax and will raise a SyntaxError.

Does `and` always return True or False?

No. `and` returns the first falsy operand it encounters, or the last operand if all are truthy. For example, `5 and 3` returns `3`, and `0 and 3` returns `0`. Wrap in `bool()` if you need a strict boolean.

Why does `0 or 'default'` return `'default'` but `0 and 'x'` return `0`?

`or` returns the first truthy value it finds. `0` is falsy, so it moves to `'default'` and returns it. `and` returns the first falsy value it finds. `0` is falsy, so it stops immediately and returns `0`.

When should I use `&` instead of `and` in Python?

Use `&` only when you are doing bitwise operations on integers or when working with NumPy or Pandas arrays where element-wise operations are needed. Use `and` for all ordinary boolean logic.

What does `not not x` mean in Python?

`not not x` is equivalent to `bool(x)`. It converts any value to its boolean equivalent by applying `not` twice. `not not 0` returns `False`; `not not 'hello'` returns `True`.

Build AI projects

A self-paced playground for building with LLMs.

TinkerLLM is FACE Prep's sister property. A guided environment for shipping real LLM applications, the kind of project that earns a paragraph on your resume, not a line.

Try TinkerLLM (₹299 launch)
Free AI Roadmap PDF