Control Flow & Logic

Master Python's control flow structures - if statements, loops, and logical operations

Overview

Control flow determines the order in which code executes. Python provides powerful, readable constructs for branching, looping, and logical operations.

Conditional Statements

Basic If Statements

PYTHON
# Simple if
age = 18
if age >= 18:
    print("You can vote")

# if-else
temperature = 25
if temperature > 30:
    print("It's hot")
else:
    print("It's comfortable")

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

Ternary Operator

PYTHON
# Basic ternary
age = 20
status = "adult" if age >= 18 else "minor"

# Nested ternary (use sparingly)
score = 75
result = "excellent" if score >= 90 else "good" if score >= 70 else "needs improvement"

# With functions
def get_discount(is_member):
    return 0.2 if is_member else 0.1

price = 100
final_price = price * (1 - get_discount(True))

Match Statement (Python 3.10+)

PYTHON
# Pattern matching
def process_command(command):
    match command.split():
        case ["quit"]:
            return "Exiting..."
        case ["hello", name]:
            return f"Hello, {name}!"
        case ["add", x, y]:
            return int(x) + int(y)
        case ["multiply", *numbers]:
            result = 1
            for n in numbers:
                result *= int(n)
            return result
        case _:
            return "Unknown command"

# Match with guards
def categorize_number(n):
    match n:
        case 0:
            return "zero"
        case n if n > 0:
            return "positive"
        case n if n < 0:
            return "negative"

# Match with types
def process_data(data):
    match data:
        case int(x) if x > 0:
            return f"Positive integer: {x}"
        case float(x):
            return f"Float value: {x}"
        case str(s) if len(s) > 0:
            return f"Non-empty string: {s}"
        case list(items) if len(items) > 0:
            return f"List with {len(items)} items"
        case _:
            return "Unhandled type"

Loops

For Loops

PYTHON
# Basic iteration
fruits = ['apple', 'banana', 'cherry']
for fruit in fruits:
    print(fruit)

# Range iteration
for i in range(5):
    print(i)  # 0, 1, 2, 3, 4

# Range with start, stop, step
for i in range(2, 10, 2):
    print(i)  # 2, 4, 6, 8

# Enumerate for index and value
for index, value in enumerate(fruits):
    print(f"{index}: {value}")

# Zip for parallel iteration
names = ['Alice', 'Bob', 'Charlie']
ages = [25, 30, 35]
for name, age in zip(names, ages):
    print(f"{name} is {age} years old")

# Dictionary iteration
person = {'name': 'John', 'age': 30, 'city': 'NYC'}
for key, value in person.items():
    print(f"{key}: {value}")

While Loops

PYTHON
# Basic while
counter = 0
while counter < 5:
    print(counter)
    counter += 1

# While with break
while True:
    user_input = input("Enter 'quit' to exit: ")
    if user_input == 'quit':
        break
    print(f"You entered: {user_input}")

# While with continue
count = 0
while count < 10:
    count += 1
    if count % 2 == 0:
        continue  # Skip even numbers
    print(count)

Loop Control Statements

PYTHON
# Break - exit loop early
for i in range(10):
    if i == 5:
        break
    print(i)  # Prints 0-4

# Continue - skip current iteration
for i in range(5):
    if i == 2:
        continue
    print(i)  # Prints 0, 1, 3, 4

# Pass - placeholder
for i in range(3):
    if i == 1:
        pass  # Do nothing, placeholder for future code
    else:
        print(i)

# Else clause (executes if loop completes without break)
for i in range(5):
    if i == 10:  # Never true
        break
else:
    print("Loop completed normally")

# While-else
n = 2
while n < 10:
    if n % 7 == 0:
        print(f"Found multiple of 7: {n}")
        break
    n += 1
else:
    print("No multiple of 7 found")

Logical Operators

Boolean Operations

PYTHON
# Basic operators
x, y = True, False
print(x and y)  # False
print(x or y)   # True
print(not x)    # False

# Short-circuit evaluation
def expensive_check():
    print("Performing expensive check...")
    return True

# 'and' short-circuits on False
result = False and expensive_check()  # expensive_check() not called

# 'or' short-circuits on True
result = True or expensive_check()  # expensive_check() not called

# Chaining comparisons
age = 25
is_valid = 18 <= age <= 65  # Equivalent to: 18 <= age and age <= 65

# Truthy and Falsy values
# Falsy: False, None, 0, 0.0, '', [], {}, set()
# Truthy: Everything else

# Using truthiness in conditions
data = []
if not data:
    print("Data is empty")

name = input("Enter name: ") or "Anonymous"  # Default value pattern

Comparison Operators

PYTHON
# Standard comparisons
a, b = 10, 20
print(a == b)  # False (equality)
print(a != b)  # True (inequality)
print(a < b)   # True (less than)
print(a <= b)  # True (less than or equal)
print(a > b)   # False (greater than)
print(a >= b)  # False (greater than or equal)

# Identity operators
list1 = [1, 2, 3]
list2 = [1, 2, 3]
list3 = list1

print(list1 == list2)  # True (same values)
print(list1 is list2)  # False (different objects)
print(list1 is list3)  # True (same object)

# Membership operators
fruits = ['apple', 'banana', 'cherry']
print('banana' in fruits)     # True
print('grape' not in fruits)  # True

# String comparisons
print('apple' < 'banana')  # True (lexicographical)
print('Apple' < 'apple')   # True (capital letters come first)

Advanced Control Flow Patterns

Guard Clauses

PYTHON
def process_order(order):
    # Early returns (guard clauses) for cleaner code
    if order is None:
        return "No order provided"
    
    if order.status == 'cancelled':
        return "Order is cancelled"
    
    if order.total <= 0:
        return "Invalid order total"
    
    # Main logic here
    return f"Processing order #{order.id}"

State Machines

PYTHON
class TrafficLight:
    def __init__(self):
        self.state = 'red'
    
    def next_state(self):
        transitions = {
            'red': 'green',
            'green': 'yellow',
            'yellow': 'red'
        }
        self.state = transitions[self.state]
        return self.state

# Usage
light = TrafficLight()
for _ in range(6):
    print(f"Light is {light.state}")
    light.next_state()

Nested Loops with Control

PYTHON
# Finding pairs with specific sum
def find_pairs_with_sum(numbers, target):
    pairs = []
    n = len(numbers)
    
    for i in range(n):
        for j in range(i + 1, n):
            if numbers[i] + numbers[j] == target:
                pairs.append((numbers[i], numbers[j]))
                break  # Break inner loop only
    
    return pairs

# Matrix traversal with conditions
def process_matrix(matrix):
    for i, row in enumerate(matrix):
        for j, value in enumerate(row):
            if value == 0:
                continue  # Skip zeros
            if value < 0:
                print(f"Negative at ({i},{j}): {value}")
                break  # Stop processing this row
            print(f"Processing ({i},{j}): {value}")

Real-World Examples

Example 1: User Authentication System

PYTHON
class AuthenticationSystem:
    def __init__(self):
        self.users = {}
        self.max_attempts = 3
        self.locked_accounts = set()
    
    def authenticate(self, username, password):
        # Check if account is locked
        if username in self.locked_accounts:
            return "Account locked. Contact support."
        
        # Check if user exists
        if username not in self.users:
            return "Invalid credentials"
        
        user = self.users[username]
        
        # Check password
        if user['password'] != password:
            user['attempts'] = user.get('attempts', 0) + 1
            
            if user['attempts'] >= self.max_attempts:
                self.locked_accounts.add(username)
                return "Account locked due to multiple failed attempts"
            
            remaining = self.max_attempts - user['attempts']
            return f"Invalid credentials. {remaining} attempts remaining"
        
        # Reset attempts on successful login
        user['attempts'] = 0
        
        # Check additional conditions
        if user.get('requires_2fa'):
            return "2FA required"
        
        if user.get('password_expired'):
            return "Password expired. Please reset."
        
        return "Login successful"

Example 2: Game Loop with State Management

PYTHON
import random

class NumberGuessingGame:
    def __init__(self, min_num=1, max_num=100):
        self.min_num = min_num
        self.max_num = max_num
        self.reset_game()
    
    def reset_game(self):
        self.target = random.randint(self.min_num, self.max_num)
        self.attempts = 0
        self.max_attempts = 10
        self.game_over = False
        self.won = False
    
    def play(self):
        print(f"Guess a number between {self.min_num} and {self.max_num}")
        
        while not self.game_over:
            # Get user input
            try:
                guess = int(input(f"Attempt {self.attempts + 1}/{self.max_attempts}: "))
            except ValueError:
                print("Please enter a valid number")
                continue
            
            self.attempts += 1
            
            # Check guess
            if guess == self.target:
                self.won = True
                self.game_over = True
                print(f"Congratulations! You won in {self.attempts} attempts!")
            elif guess < self.target:
                print("Too low!")
            else:
                print("Too high!")
            
            # Check if max attempts reached
            if self.attempts >= self.max_attempts:
                self.game_over = True
                if not self.won:
                    print(f"Game over! The number was {self.target}")
            
            # Give hints
            if not self.game_over and self.attempts == self.max_attempts - 2:
                if self.target % 2 == 0:
                    print("Hint: The number is even")
                else:
                    print("Hint: The number is odd")
        
        # Ask to play again
        play_again = input("Play again? (y/n): ").lower()
        if play_again == 'y':
            self.reset_game()
            self.play()

# Usage
game = NumberGuessingGame()
# game.play()

Example 3: Data Validation Pipeline

PYTHON
class DataValidator:
    def __init__(self):
        self.errors = []
        self.warnings = []
    
    def validate_email(self, email):
        if not email:
            self.errors.append("Email is required")
            return False
        
        if '@' not in email:
            self.errors.append("Invalid email format")
            return False
        
        domain = email.split('@')[1]
        if '.' not in domain:
            self.errors.append("Invalid email domain")
            return False
        
        return True
    
    def validate_age(self, age):
        try:
            age_int = int(age)
        except (ValueError, TypeError):
            self.errors.append("Age must be a number")
            return False
        
        if age_int < 0:
            self.errors.append("Age cannot be negative")
            return False
        elif age_int < 13:
            self.warnings.append("User is under 13")
        elif age_int > 120:
            self.warnings.append("Unusual age value")
        
        return True
    
    def validate_password(self, password):
        if len(password) < 8:
            self.errors.append("Password must be at least 8 characters")
            return False
        
        has_upper = any(c.isupper() for c in password)
        has_lower = any(c.islower() for c in password)
        has_digit = any(c.isdigit() for c in password)
        has_special = any(c in '!@#$%^&*' for c in password)
        
        if not all([has_upper, has_lower, has_digit]):
            self.errors.append("Password must contain uppercase, lowercase, and digits")
            return False
        
        if not has_special:
            self.warnings.append("Consider adding special characters for stronger password")
        
        return True
    
    def validate_user(self, user_data):
        self.errors = []
        self.warnings = []
        
        # Validate each field
        email_valid = self.validate_email(user_data.get('email'))
        age_valid = self.validate_age(user_data.get('age'))
        password_valid = self.validate_password(user_data.get('password'))
        
        # Overall validation result
        is_valid = email_valid and age_valid and password_valid
        
        return {
            'valid': is_valid,
            'errors': self.errors,
            'warnings': self.warnings
        }

# Usage
validator = DataValidator()
user = {
    'email': 'user@example.com',
    'age': '25',
    'password': 'SecurePass123'
}
result = validator.validate_user(user)
print(f"Valid: {result['valid']}")
if result['errors']:
    print(f"Errors: {result['errors']}")
if result['warnings']:
    print(f"Warnings: {result['warnings']}")

Practice Exercises

Exercise 1: Smart Menu System

PYTHON
# TODO: Create an interactive menu system
def create_menu_system():
    """
    Create a menu system that:
    - Displays options
    - Handles invalid input
    - Supports submenus
    - Remembers user choices
    - Provides help text
    """
    # Your code here
    pass

# Test your implementation
# menu = create_menu_system()
# menu.run()

Exercise 2: Pattern Detector

PYTHON
# TODO: Implement pattern detection in sequences
def detect_patterns(sequence):
    """
    Detect patterns in a sequence:
    - Arithmetic progression
    - Geometric progression
    - Fibonacci-like
    - Palindrome
    - Repeating pattern
    Return pattern type and next predicted values
    """
    # Your code here
    pass

# Test cases
test_sequences = [
    [2, 4, 6, 8],        # Arithmetic
    [1, 2, 4, 8],        # Geometric
    [1, 1, 2, 3, 5],     # Fibonacci
    [1, 2, 3, 2, 1],     # Palindrome
    [1, 2, 1, 2, 1, 2]   # Repeating
]

Exercise 3: Advanced Calculator

PYTHON
# TODO: Build a calculator with control flow
class SmartCalculator:
    """
    Implement a calculator that:
    - Handles multiple operations
    - Supports operation history
    - Includes memory functions
    - Validates input
    - Provides undo functionality
    """
    def __init__(self):
        # Your initialization here
        pass
    
    def calculate(self, expression):
        # Your code here
        pass
    
    def undo(self):
        # Your code here
        pass

# Usage example
# calc = SmartCalculator()
# result = calc.calculate("2 + 3 * 4")

Best Practices

1. Use guard clauses for early returns to reduce nesting 2. Prefer elif over nested if for readability 3. Use match/case for complex branching (Python 3.10+) 4. Avoid deep nesting - consider refactoring 5. Use enumerate() instead of range(len()) for iteration 6. Leverage else clauses in loops for completion detection 7. Short-circuit evaluation for performance 8. Meaningful variable names in loops 9. Break complex conditions into variables 10. Document complex logic with comments

Common Pitfalls

PYTHON
# Pitfall 1: Modifying list while iterating
numbers = [1, 2, 3, 4, 5]
# Wrong:
for n in numbers:
    if n % 2 == 0:
        numbers.remove(n)  # Causes issues

# Correct:
numbers = [n for n in numbers if n % 2 != 0]

# Pitfall 2: Mutable default in conditions
def process_items(items, processed_list=[]):  # Bad!
    for item in items:
        if item not in processed_list:
            processed_list.append(item)
    return processed_list

# Correct:
def process_items(items, processed_list=None):
    if processed_list is None:
        processed_list = []
    # ... rest of function

# Pitfall 3: Infinite loops
# Wrong:
i = 0
while i < 10:
    print(i)
    # Forgot to increment i!

# Correct:
i = 0
while i < 10:
    print(i)
    i += 1

Performance Tips

  • Use set for membership testing in loops
  • Prefer list comprehensions over loops for simple transformations
  • Use generators for large sequences
  • Cache expensive computations outside loops
  • Profile code to identify bottlenecks
  • Consider itertools for efficient iteration patterns