Python stops on the line the moment it meets && inside an if statement, and the caret lands under the second ampersand. Every C-family language accepts those two characters for logical conjunction, so a parser error reads like a bug rather than a language rule. The replacement is the and keyword, and it behaves differently from && in two ways that change your code: it returns one of its operands, and it sometimes skips the second one entirely.

Why && is a syntax error in Python

The characters & and && both exist in Python, and neither one means logical conjunction. A single & is the bitwise AND operator, and a doubled && is not a token the grammar recognises at all.

name1 = "Kundan"
name2 = "Rohan"

if name1 == "Kundan" && name2 == "Rohan":
    print("Hello Kundan and Rohan")
Python rejects && while it reads the line, and the caret marks the exact characters it cannot parse.

The traceback points at line 4 and puts the caret under the ampersands rather than the comparison. That position is the useful part, because it says the grammar never reached the operands.

A single & parses, which makes the mistake harder to spot.

print("2 & 3      ->", 2 & 3)
print("True & False ->", True & False)
2 & 3      -> 2
True & False -> False

The bitwise version returns 2 for the same input, because it compares the bits of 2 and 3 instead of deciding anything about truth. Reaching for & when you meant logical conjunction is how a condition ends up true in cases you never tested.

Checking the exact error text is worth the habit, because a missing and looks like a missing bracket at a glance and the two need different fixes.

Using and in an if statement

The and keyword joins two conditions, and the block runs only when both of them are truthy.

name1 = "Kundan"
name2 = "Rohan"

if name1 == "Kundan" and name2 == "Rohan":
    print("Hello Kundan and Rohan")

Nesting two if statements produces the same outcome, at the cost of an extra indent level.

value = 7

if value > 0:
    if value % 2 == 1:
        print("positive odd number")

if value > 0 and value % 2 == 1:
    print("positive odd number")
positive odd number
positive odd number

Both forms print the same line. The single-line version reads as one decision instead of two, and it keeps the two conditions visible next to each other rather than pushing the second one down an indent level.

Nesting also grows badly, because three conditions mean three indent levels and a block that is mostly whitespace.

user = "kundan"
active = True
attempts = 2

if user == "kundan" and active and attempts < 3:
    print("sign-in allowed")
else:
    print("sign-in blocked")

What and actually returns

The operator does not convert its operands to booleans and hand one back. It returns the operand that decided the outcome, whatever type that operand happens to be.

print("1 and 2     ->", repr(1 and 2))
print("0 and 2     ->", repr(0 and 2))
print("'a' and 'b' ->", repr("a" and "b"))
print("'' and 'b'  ->", repr("" and "b"))
print("type        ->", type(1 and 2).__name__)
Terminal output showing 1 and 2 returning 2, 0 and 2 returning 0, 'a' and 'b' returning 'b', an empty string and 'b' returning the empty string, and the type of 1 and 2 being int. Command: python3 and_returns.py.
The result carries the type of whichever operand decided the outcome. It is not a boolean.

When the left operand is truthy, the expression hands back the right one, so 1 and 2 is 2 rather than True. When the left operand is falsy, the right one never gets a say and the left one comes back unchanged.

That is why the printed type is int. Code that assumes a boolean from and will still work inside an if statement, because the result is evaluated for truthiness anyway, and it breaks the moment you store the value and compare it to True.

result = 1 and 2
print("result is True :", result is True)
print("result == True :", result == True)
print("bool(result)   :", bool(result))
result is True : False
result == True : False
bool(result)   : True

Both comparisons return False, and only the last line reports what the branch would have seen. Storing the raw result and testing it against True is the mistake the snippet exposes.

The truth table for and

Two operands give four combinations, and and returns the first falsy one it meets. When neither is falsy, it returns the last operand.

def truth(a, b):
    return a and b

print(f"{'A':<6}{'B':<6}{'A and B'}")
for a in (True, False):
    for b in (True, False):
        print(f"{str(a):<6}{str(b):<6}{truth(a, b)}")
A     B     A and B
True  True  True
True  False False
False True  False
False False False
A B A and B Operand returned
True True True B, because A was truthy
True False False B, because A was truthy
False True False A, because A was falsy
False False False A, because A was falsy

Read the last column first. The block inside an if statement runs only on the first row, and the value that came back identifies which operand ended the evaluation.

Every row below the first returns a falsy value, so the branch is skipped even though one of the two operands was true.

Short-circuit evaluation

Python stops evaluating as soon as the answer is settled, which means a falsy left operand prevents the right one from running at all. A function call on the right side is the clearest way to see it.

def side_effect(label):
    print(f"   evaluated {label}")
    return True

print("case 1: left operand False")
result = False and side_effect("right")
print("   result:", result)

print("case 2: left operand True")
result = True and side_effect("right")
print("   result:", result)
Terminal output showing that False and check('right') prints only result: False, while True and check('right') prints evaluated right before result: True. Command: python3 short_circuit.py.
The check function runs in the second case and never runs in the first, which is short-circuit evaluation made visible.

The second case prints the evaluated line and the first does not. Nothing was skipped by accident, and the same rule that saves a wasted call can hide a call you were counting on.

Left operand Right operand evaluated Value returned
Truthy Yes The right operand
Falsy No The left operand

Guard clauses depend on this behavior, because the right operand only runs when the left one has already made it safe.

readings = [("sensor-a", 21.4), ("sensor-b", None), ("sensor-c", 19.8)]

for name, value in readings:
    if value is not None and value < 20:
        print(f"{name}: {value} below threshold")
    else:
        print(f"{name}: no alert")
sensor-a: no alert
sensor-b: no alert
sensor-c: 19.8 below threshold

Reversing those two conditions raises a TypeError on the missing reading, so the order is not cosmetic. The None check has to come first for the comparison to ever be reached.

Loops lean on the same property. A while loop that walks a list and checks a running total stops as soon as either condition fails, without an extra pass to notice.

queue = [3, 1, 4, 1, 5]
total = 0
index = 0

while index < len(queue) and total < 6:
    total += queue[index]
    index += 1

print("consumed:", queue[:index])
print("total:", total)
consumed: [3, 1, 4]
total: 8

Precedence and chained comparisons

and binds tighter than or, so an expression without brackets groups itself in a way that is easy to misread.

print("True or False and False   ->", True or False and False)
print("(True or False) and False ->", (True or False) and False)
print("True and False or True    ->", True and False or True)
True or False and False   -> True
(True or False) and False -> False
True and False or True    -> True

Brackets are the only difference between the first two expressions, and they return opposite answers. Brackets cost nothing and remove the need to remember which operator wins.

Binds tightest Operator Example
1 Comparisons a == b, a < b
2 not not a
3 and a and b
4 (loosest) or a or b

For a range check, chained comparison says the same thing as two comparisons joined by and.

age = 25
using_and = age > 18 and age < 65
chained = 18 < age < 65
print("age > 18 and age < 65 :", using_and)
print("18 < age < 65         :", chained)
print("same result           :", using_and == chained)
age > 18 and age < 65 : True
18 < age < 65         : True
same result           : True

The or condition that always runs

The version of this mistake that hurts most uses or, because the condition stays valid Python and the branch takes the wrong turn.

status = "pending"
if status == "open" or "active":
    print("this line runs even though status is pending")
else:
    print("this line never runs")
this line runs even though status is pending

The second operand is the non-empty string active, which is truthy on its own, so the whole condition is true no matter what status holds. Comparing both operands fixes it.

status = "pending"
if status == "open" or status == "active":
    print("status is open or active")
else:
    print("status is neither open nor active")
status is neither open nor active

The same trap waits with and. Writing if value and value > 0 works, while if value > 0 and value does not, because the bare name is truthy for every value except zero and an empty container.

The fix is to compare against something explicit rather than letting truthiness decide, so a zero reading and a missing reading take different branches.

Printing the condition on its own is the fastest diagnosis. If it evaluates to a string or a number instead of True, one of the operands is answering the question by itself.

for value in (0, 1, "", "text", [], [0], None):
    if value:
        print(f"{value!r:<8} is truthy")
    else:
        print(f"{value!r:<8} is falsy")
0        is falsy
1        is truthy
''       is falsy
'text'   is truthy
[]       is falsy
[0]      is truthy
None     is falsy

Common questions about and in Python

The cases below produce a wrong branch rather than an error, which is what makes them worth checking first.

What is the Python equivalent of &&?

Use the and keyword. Python has no && operator, so the C, Java and JavaScript form raises SyntaxError: invalid syntax at the position of the ampersands.

Does and always return True or False?

It does not. and returns one of its operands, so 1 and 2 evaluates to 2 and an empty string and something evaluates to the empty string. Wrap the expression in bool when you need a real boolean value.

What is short-circuit evaluation in Python?

Python stops evaluating an and expression as soon as it finds a falsy operand, so the right side never runs in that case. The same rule lets you write a bounds or None check on the left and a comparison on the right without raising an error.

Which operator runs first, and or or?

and binds tighter than or, so True or False and False evaluates the and first and returns True. Add brackets whenever the grouping is not obvious to a reader.

Can you use and with more than two conditions?

You can chain as many as you like, and the expression stops at the first falsy one. For a range check, 18 < age < 65 is shorter than joining two comparisons with and and returns the same result.

Share.
Leave A Reply