Handle a Python Exception within While a loop

I was building a data processing system for a client that needed to continuously monitor stock prices from an API. The challenge was that network issues, API rate limits, and data format errors kept crashing my while loop.

I’ve learned that handling exceptions within while loops is crucial for building robust applications. Without proper exception handling, a single error can terminate your entire loop and crash your program.

In this article, I’ll share four proven methods I use to handle exceptions in while loops, complete with real-world examples that you can implement immediately.

Method 1 – Basic Try-Except Within While Loop

The simplest way to handle exceptions in a while loop is to wrap your code in a try-except block. This prevents the loop from terminating when an error occurs.

I use this method when I want the loop to continue running regardless of errors. Here’s how it works:

import time
import random

def process_customer_data():
    """Simulate processing customer data that might fail"""
    customers = ["John Smith", "Sarah Johnson", "Mike Davis", "Lisa Wilson", "Tom Brown"]
    counter = 0

    while counter < 10:
        try:
            # Simulate random errors that might occur in real applications
            if random.random() < 0.3:  # 30% chance of error
                raise ValueError("Database connection failed")

            if random.random() < 0.2:  # 20% chance of another error
                raise ConnectionError("API timeout occurred")

            # Process a random customer
            customer = random.choice(customers)
            processing_time = random.uniform(0.5, 2.0)
            time.sleep(processing_time)

            print(f"✓ Successfully processed customer: {customer}")
            counter += 1

        except ValueError as ve:
            print(f"✗ Database error: {ve}")
        except ConnectionError as ce:
            print(f"✗ Network error: {ce}")
        except Exception as e:
            print(f"✗ Unexpected error: {e}")

        # Small delay before next iteration
        time.sleep(0.5)

    print(f"\nCompleted processing {counter} customers")

# Run the function
process_customer_data()

I executed the above example code and added the screenshot below.

while try

This approach ensures that even if database connections fail or API calls timeout, your loop continues processing other customers.

Method 2 – Continue Statement with Exception Handling

Sometimes you want to skip the current iteration when an error occurs and immediately move to the next one. The continue statement is perfect for this scenario.

I frequently use this method when processing large datasets where some records might be corrupted or incomplete:

import json
import time

def process_sales_data():
    """Process sales data with error handling and continue statement"""

    # Sample sales data that might have errors
    sales_records = [
        '{"customer": "Walmart", "amount": 15000, "date": "2024-01-15"}',
        '{"customer": "Target", "amount": 12000, "date": "2024-01-16"}',
        '{invalid_json}',  # This will cause an error
        '{"customer": "Amazon", "amount": 25000, "date": "2024-01-17"}',
        '{"customer": "Costco", "amount": null}',  # Missing amount
        '{"customer": "Home Depot", "amount": 18000, "date": "2024-01-18"}',
    ]

    total_sales = 0
    processed_count = 0
    current_index = 0

    while current_index < len(sales_records):
        try:
            # Parse JSON data
            record = json.loads(sales_records[current_index])

            # Validate required fields
            if not record.get('customer'):
                raise ValueError("Missing customer name")

            if not record.get('amount') or record['amount'] is None:
                raise ValueError("Invalid sales amount")

            # Process valid record
            customer = record['customer']
            amount = float(record['amount'])

            total_sales += amount
            processed_count += 1

            print(f"✓ Processed: {customer} - ${amount:,.2f}")

        except json.JSONDecodeError:
            print(f"✗ Record {current_index + 1}: Invalid JSON format - skipping")
            current_index += 1
            continue

        except ValueError as ve:
            print(f"✗ Record {current_index + 1}: {ve} - skipping")
            current_index += 1
            continue

        except Exception as e:
            print(f"✗ Record {current_index + 1}: Unexpected error {e} - skipping")
            current_index += 1
            continue

        current_index += 1
        time.sleep(0.1)  # Small delay for demonstration

    print(f"\n--- Sales Summary ---")
    print(f"Total records processed: {processed_count}")
    print(f"Total sales: ${total_sales:,.2f}")
    print(f"Average sale: ${total_sales/processed_count if processed_count > 0 else 0:,.2f}")

# Execute the function
process_sales_data()

I executed the above example code and added the screenshot below.

python while except

The continue statement immediately jumps to the next iteration, skipping any remaining code in the current loop cycle.

This is particularly useful when you’re processing files or API responses where some data might be malformed.

Method 3 – Break Statement for Critical Errors

Not all errors should be ignored. Some exceptions indicate critical problems that require immediate attention and should stop the loop entirely.

I use the break statement when encountering errors that make it impossible or unsafe to continue:

import random
import time
from datetime import datetime

def monitor_server_health():
    """Monitor server health with critical error handling"""

    max_attempts = 20
    attempt_count = 0
    consecutive_failures = 0
    max_consecutive_failures = 3

    print("Starting server health monitoring...")
    print("=" * 50)

    while attempt_count < max_attempts:
        try:
            attempt_count += 1
            current_time = datetime.now().strftime("%H:%M:%S")

            # Simulate server health check
            cpu_usage = random.uniform(10, 95)
            memory_usage = random.uniform(20, 90)
            disk_usage = random.uniform(30, 85)

            # Simulate different types of errors
            if random.random() < 0.1:  # 10% chance of network error
                raise ConnectionError("Cannot connect to server")

            if random.random() < 0.05:  # 5% chance of authentication error
                raise PermissionError("Authentication failed - invalid credentials")

            if random.random() < 0.03:  # 3% chance of critical system error
                raise SystemError("Critical system failure detected")

            # Check for concerning metrics
            if cpu_usage > 90:
                raise ResourceWarning(f"High CPU usage: {cpu_usage:.1f}%")

            if memory_usage > 85:
                raise ResourceWarning(f"High memory usage: {memory_usage:.1f}%")

            # Successful health check
            consecutive_failures = 0  # Reset failure counter
            print(f"[{current_time}] ✓ Server OK - CPU: {cpu_usage:.1f}%, RAM: {memory_usage:.1f}%, Disk: {disk_usage:.1f}%")

        except ConnectionError as ce:
            consecutive_failures += 1
            print(f"[{current_time}] ⚠ Network issue: {ce}")

            if consecutive_failures >= max_consecutive_failures:
                print(f"\n❌ CRITICAL: {consecutive_failures} consecutive network failures!")
                print("Stopping monitoring - manual intervention required")
                break

        except PermissionError as pe:
            print(f"[{current_time}] ❌ CRITICAL: {pe}")
            print("Authentication failure - stopping monitoring")
            break

        except SystemError as se:
            print(f"[{current_time}] 🚨 EMERGENCY: {se}")
            print("Critical system error detected - immediate shutdown required")
            break

        except ResourceWarning as rw:
            consecutive_failures += 1
            print(f"[{current_time}] ⚠ Performance warning: {rw}")

        except Exception as e:
            consecutive_failures += 1
            print(f"[{current_time}] ✗ Unexpected error: {e}")

        time.sleep(2)  # Wait 2 seconds between checks

    print(f"\nMonitoring completed after {attempt_count} attempts")
    if consecutive_failures >= max_consecutive_failures:
        print("⚠ Action required: Check server status manually")

# Run server monitoring
monitor_server_health()

I executed the above example code and added the screenshot below.

while except python

In this example, authentication failures and system errors trigger a break statement because continuing would be pointless or dangerous.

Network issues are treated as warnings, but too many consecutive failures also trigger a shutdown.

Method 4 – Advanced Exception Handling with Logging

For production applications, I always implement comprehensive logging alongside exception handling. This helps with debugging and monitoring application health.

Here’s my go-to approach for robust exception handling in while loops:

import logging
import time
import random
import json
from datetime import datetime
from typing import Dict, List

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('payment_processor.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

class PaymentProcessor:
    def __init__(self):
        self.processed_payments = []
        self.failed_payments = []
        self.retry_queue = []
        
    def process_payment_queue(self, payment_queue: List[Dict], max_runtime_minutes: int = 5):
        """Process payment queue with advanced exception handling"""
        
        start_time = datetime.now()
        max_runtime_seconds = max_runtime_minutes * 60
        
        payment_index = 0
        retry_attempts = {}
        max_retries = 3
        
        logger.info(f"Starting payment processing - {len(payment_queue)} payments in queue")
        
        while payment_index < len(payment_queue):
            # Check runtime limit
            elapsed_time = (datetime.now() - start_time).total_seconds()
            if elapsed_time > max_runtime_seconds:
                logger.warning(f"Runtime limit reached ({max_runtime_minutes} minutes)")
                break
            
            current_payment = payment_queue[payment_index]
            payment_id = current_payment.get('id', f'payment_{payment_index}')
            
            try:
                # Validate payment data
                self._validate_payment(current_payment)
                
                # Process payment (simulate API call)
                result = self._process_single_payment(current_payment)
                
                # Success
                self.processed_payments.append({
                    'payment_id': payment_id,
                    'amount': current_payment['amount'],
                    'timestamp': datetime.now().isoformat(),
                    'result': result
                })
                
                logger.info(f"✓ Payment {payment_id} processed successfully - ${current_payment['amount']:.2f}")
                
            except ValueError as ve:
                  # Data validation errors - don't retry
                error_msg = f"Validation failed for payment {payment_id}: {ve}"
                logger.error(error_msg)
                
                self.failed_payments.append({
                    'payment_id': payment_id,
                    'error': str(ve),
                    'error_type': 'validation_error',
                    'timestamp': datetime.now().isoformat()
                })
                
            except ConnectionError as ce:
                # Network errors - retry up to max_retries
                retry_count = retry_attempts.get(payment_id, 0)
                
                if retry_count < max_retries:
                    retry_attempts[payment_id] = retry_count + 1
                    logger.warning(f"Network error for payment {payment_id} (attempt {retry_count + 1}): {ce}")
                    
                    # Add to retry queue and continue
                    self.retry_queue.append(current_payment)
                    time.sleep(2 ** retry_count)  # Exponential backoff
                    continue
                else:
                    logger.error(f"Payment {payment_id} failed after {max_retries} attempts: {ce}")
                    self.failed_payments.append({
                        'payment_id': payment_id,
                        'error': str(ce),
                        'error_type': 'network_error',
                        'retry_attempts': retry_count,
                        'timestamp': datetime.now().isoformat()
                    })
                    
            except PermissionError as pe:
                # Authentication/authorization errors - critical
                logger.critical(f"Authentication failed for payment {payment_id}: {pe}")
                logger.critical("Stopping payment processing - check API credentials")
                break
                
            except Exception as e:
                # Unexpected errors - log and continue
                logger.error(f"Unexpected error processing payment {payment_id}: {e}", exc_info=True)
                
                self.failed_payments.append({
                    'payment_id': payment_id,
                    'error': str(e),
                    'error_type': 'unexpected_error',
                    'timestamp': datetime.now().isoformat()
                })
                
            finally:
                # This runs regardless of success or failure
                payment_index += 1
                
                # Progress update every 10 payments
                if payment_index % 10 == 0:
                    logger.info(f"Progress: {payment_index}/{len(payment_queue)} payments processed")
        
        # Generate summary report
        self._generate_processing_report()
    
    def _validate_payment(self, payment: Dict):
        """Validate payment data"""
        required_fields = ['customer_id', 'amount', 'payment_method']
        
        for field in required_fields:
            if field not in payment or payment[field] is None:
                raise ValueError(f"Missing required field: {field}")
        
        if payment['amount'] <= 0:
            raise ValueError("Payment amount must be positive")
            
        if payment['amount'] > 10000:
            raise ValueError("Payment amount exceeds daily limit")
    
    def _process_single_payment(self, payment: Dict):
        """Simulate payment processing"""
        # Simulate various error conditions
        error_chance = random.random()
        
        if error_chance < 0.1:  # 10% network error
            raise ConnectionError("Payment gateway timeout")
        elif error_chance < 0.15:  # 5% auth error
            raise PermissionError("Invalid API key")
        elif error_chance < 0.18:  # 3% unexpected error
            raise RuntimeError("Payment gateway internal error")
        
        # Simulate processing time
        time.sleep(random.uniform(0.1, 0.5))
        
        return {
            'transaction_id': f"txn_{random.randint(100000, 999999)}",
            'status': 'completed',
            'processing_time_ms': random.randint(100, 500)
        }
    
    def _generate_processing_report(self):
        """Generate comprehensive processing report"""
        total_payments = len(self.processed_payments) + len(self.failed_payments)
        success_rate = (len(self.processed_payments) / total_payments * 100) if total_payments > 0 else 0
        
        total_amount = sum(p['amount'] for p in self.processed_payments)
        
        logger.info("=" * 60)
        logger.info("PAYMENT PROCESSING SUMMARY")
        logger.info("=" * 60)
        logger.info(f"Total Payments: {total_payments}")
        logger.info(f"Successful: {len(self.processed_payments)}")
        logger.info(f"Failed: {len(self.failed_payments)}")
        logger.info(f"Success Rate: {success_rate:.1f}%")
        logger.info(f"Total Amount Processed: ${total_amount:,.2f}")
        logger.info(f"Payments in Retry Queue: {len(self.retry_queue)}")
        
        # Error breakdown
        if self.failed_payments:
            error_types = {}
            for failure in self.failed_payments:
                error_type = failure['error_type']
                error_types[error_type] = error_types.get(error_type, 0) + 1
            
            logger.info("\nError Breakdown:")
            for error_type, count in error_types.items():
                logger.info(f"  {error_type}: {count}")

# Example usage
def run_payment_processing_example():
    """Run the payment processing example"""
    
    # Sample payment data
    sample_payments = [
        {'id': 'pay_001', 'customer_id': 'cust_123', 'amount': 99.99, 'payment_method': 'credit_card'},
        {'id': 'pay_002', 'customer_id': 'cust_456', 'amount': 149.50, 'payment_method': 'debit_card'},
        {'id': 'pay_003', 'customer_id': 'cust_789', 'amount': -50.00, 'payment_method': 'credit_card'},  # Invalid amount
        {'id': 'pay_004', 'customer_id': 'cust_101', 'amount': 75.25, 'payment_method': 'paypal'},
        {'id': 'pay_005', 'customer_id': None, 'amount': 200.00, 'payment_method': 'credit_card'},  # Missing customer_id
        {'id': 'pay_006', 'customer_id': 'cust_202', 'amount': 125.75, 'payment_method': 'apple_pay'},
        {'id': 'pay_007', 'customer_id': 'cust_303', 'amount': 300.00, 'payment_method': 'credit_card'},
        {'id': 'pay_008', 'customer_id': 'cust_404', 'amount': 15000.00, 'payment_method': 'wire_transfer'},  # Exceeds limit
        {'id': 'pay_009', 'customer_id': 'cust_505', 'amount': 89.99, 'payment_method': 'credit_card'},
        {'id': 'pay_010', 'customer_id': 'cust_606', 'amount': 175.50, 'payment_method': 'debit_card'},
    ]
    
    processor = PaymentProcessor()
    processor.process_payment_queue(sample_payments, max_runtime_minutes=2)

# Run the example
run_payment_processing_example()

This advanced approach combines multiple exception handling strategies with comprehensive logging and retry logic.

The finally block ensures that the loop index always increments, preventing infinite loops even when exceptions occur.

Conclusion

The four methods I’ve shared, basic try-except blocks, continue statements for recoverable errors, break statements for critical failures, and advanced logging with custom exceptions, have saved me countless hours of debugging and prevented numerous system crashes.

Remember these key principles: catch specific exceptions rather than using broad except clauses, use continue for recoverable errors that shouldn’t stop your loop, implement break for critical failures that require immediate attention, and always log your exceptions for easier troubleshooting.

You may also read:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.