Introduction
Logic errors are bugs where the code runs without crashing but produces incorrect results. Unlike syntax errors (which the compiler catches) or runtime errors (which throw exceptions), logic errors are silent — the program behaves differently from what you intended. Finding them requires understanding common patterns that lead to wrong output: off-by-one errors, incorrect boolean conditions, wrong operator precedence, unintended integer division, and mutation of shared state.
Off-by-One Errors
The most common logic error. Happens with loop boundaries, array indices, and string slicing:
1# WRONG — misses the last element
2items = [10, 20, 30, 40, 50]
3for i in range(len(items) - 1): # Goes 0..3, skips index 4
4 print(items[i])
5# Prints 10, 20, 30, 40 — misses 50
6
7# CORRECT
8for i in range(len(items)): # Goes 0..4
9 print(items[i])
10
11# WRONG — fence post error in counting
12def count_between(a, b):
13 return b - a # For a=1, b=5: returns 4 but there are 5 numbers (1,2,3,4,5)
14
15# CORRECT
16def count_between(a, b):
17 return b - a + 1 # 5 - 1 + 1 = 5
Incorrect Boolean Logic
Confusing AND/OR, negation, and De Morgan's laws:
1# WRONG — should be AND, not OR
2age = 25
3if age < 18 or age > 65: # True for 25? No. But intent was to check working age
4 print("Working age")
5
6# CORRECT — working age is >= 18 AND <= 65
7if age >= 18 and age <= 65:
8 print("Working age")
9
10# WRONG — De Morgan's law mistake
11# "not (A and B)" is NOT the same as "not A and not B"
12if not (is_admin and is_active): # True when EITHER is false
13 deny_access()
14
15# This is different:
16if not is_admin and not is_active: # True only when BOTH are false
17 deny_access()
Operator Precedence
1# WRONG — & has lower precedence than ==
2result = x & 0xFF == 0 # Parsed as: x & (0xFF == 0) → x & False → 0
3# CORRECT
4result = (x & 0xFF) == 0
5
6# WRONG — assignment vs comparison
7x = 5
8if x = 10: # SyntaxError in Python, but in C: if (x = 10) — always true!
9 pass
10
11# JavaScript pitfall
12if (x == "5") { } // true — type coercion
13if (x === "5") { } // false — strict comparison
Integer Division and Floating Point
1# WRONG — integer division truncates
2average = sum(scores) // len(scores) # 7 // 2 = 3, not 3.5
3
4# CORRECT
5average = sum(scores) / len(scores) # 7 / 2 = 3.5
6
7# Floating point comparison
8# WRONG
9if 0.1 + 0.2 == 0.3: # False! 0.1 + 0.2 = 0.30000000000000004
10 print("Equal")
11
12# CORRECT
13if abs((0.1 + 0.2) - 0.3) < 1e-9:
14 print("Equal")
Mutation of Shared State
1# WRONG — mutable default argument
2def add_item(item, items=[]):
3 items.append(item)
4 return items
5
6print(add_item("a")) # ['a']
7print(add_item("b")) # ['a', 'b'] — not ['b']!
8
9# CORRECT
10def add_item(item, items=None):
11 if items is None:
12 items = []
13 items.append(item)
14 return items
15
16# WRONG — modifying list while iterating
17numbers = [1, 2, 3, 4, 5, 6]
18for n in numbers:
19 if n % 2 == 0:
20 numbers.remove(n)
21print(numbers) # [1, 3, 5, 6] — 6 survived! Iterator skipped it.
22
23# CORRECT — iterate over a copy or use list comprehension
24numbers = [n for n in numbers if n % 2 != 0]
Wrong Variable Scope
1# WRONG — variable shadowing
2total = 100
3
4def calculate():
5 total = 0 # This is a LOCAL variable, not the global one
6 for i in range(10):
7 total += i
8 # Global total is still 100
9
10# CORRECT — use nonlocal or return
11def calculate():
12 local_total = 0
13 for i in range(10):
14 local_total += i
15 return local_total
16
17total = calculate()
Debugging Strategies
1# 1. Add print statements at key points
2def binary_search(arr, target):
3 lo, hi = 0, len(arr) - 1
4 while lo <= hi:
5 mid = (lo + hi) // 2
6 print(f"lo={lo}, hi={hi}, mid={mid}, arr[mid]={arr[mid]}")
7 if arr[mid] == target:
8 return mid
9 elif arr[mid] < target:
10 lo = mid + 1
11 else:
12 hi = mid - 1
13 return -1
14
15# 2. Use assert to verify assumptions
16def divide(a, b):
17 assert b != 0, "Divisor must not be zero"
18 result = a / b
19 assert isinstance(result, float), f"Expected float, got {type(result)}"
20 return result
21
22# 3. Test edge cases
23assert binary_search([], 5) == -1 # Empty array
24assert binary_search([5], 5) == 0 # Single element
25assert binary_search([1, 2, 3], 4) == -1 # Not found
26assert binary_search([1, 2, 3], 1) == 0 # First element
27assert binary_search([1, 2, 3], 3) == 2 # Last element
Common Pitfalls
Confusing = and ==: In languages like C, Java, and JavaScript, if (x = 5) is an assignment (always truthy), not a comparison. Python prevents this with a syntax error, but == vs is is a similar trap for object identity checks.
Short-circuit evaluation surprises: if a and b does not evaluate b if a is falsy. If b has side effects (like function calls), they are skipped. Similarly, if a or b skips b if a is truthy.
Return inside a loop: Placing return inside a loop exits on the first iteration. If you intend to process all items, accumulate results and return after the loop ends.
Forgetting break in switch/case (C, Java, JS): Without break, execution falls through to the next case. Python's match/case does not have this problem, but C-family languages do.
Using == for floating point comparison: Floating point arithmetic introduces rounding errors. 0.1 + 0.2 != 0.3 in most languages. Use an epsilon tolerance or dedicated comparison functions.
Summary
Logic errors produce wrong results without crashing — they are the hardest bugs to find
Off-by-one errors are the most common: double-check loop boundaries and index ranges
Verify boolean conditions with truth tables, especially when using and, or, and not
Watch for integer division truncation, floating point imprecision, and mutable default arguments
Use print debugging, assertions, and edge-case tests to isolate where the logic diverges from intent