Coming Soon

This lesson is currently being developed

Detecting and handling errors

Master error detection and handling strategies.

Error Detection and Handling
Chapter
Beginner
Difficulty
40min
Estimated Time

What to Expect

Comprehensive explanations with practical examples

Interactive coding exercises to practice concepts

Knowledge quiz to test your understanding

Step-by-step guidance for beginners

Development Status

In Progress

Content is being carefully crafted to provide the best learning experience

Preview

Early Preview Content

This content is still being developed and may change before publication.

9.4 — Detecting and handling errors

In this lesson, you'll learn strategies for detecting errors in your programs and handling them gracefully when they occur.

What is error detection and handling?

Error detection is the process of identifying when something goes wrong in your program. Error handling is deciding what to do when an error is detected. Together, they help create robust programs that can deal with unexpected situations without crashing.

Think of error handling like defensive driving - you anticipate problems (other drivers, bad weather, mechanical issues) and have plans to handle them safely.

Types of errors to detect

Runtime errors

Errors that occur during program execution:

#include <iostream>

int divide(int numerator, int denominator)
{
    // Problem: Division by zero will crash the program
    return numerator / denominator;
}

int safeDivide(int numerator, int denominator, bool& success)
{
    // Error detection: Check for division by zero
    if (denominator == 0)
    {
        std::cout << "Error: Cannot divide by zero!" << std::endl;
        success = false;
        return 0;  // Return safe default value
    }
    
    success = true;
    return numerator / denominator;
}

int main()
{
    std::cout << "Unsafe division:" << std::endl;
    // This would crash: std::cout << divide(10, 0) << std::endl;
    std::cout << "Crash prevented for demonstration" << std::endl;
    
    std::cout << "\nSafe division:" << std::endl;
    bool success;
    int result = safeDivide(10, 0, success);
    
    if (success)
        std::cout << "Result: " << result << std::endl;
    else
        std::cout << "Division failed - using default value: " << result << std::endl;
    
    return 0;
}

Output:

Unsafe division:
Crash prevented for demonstration

Safe division:
Error: Cannot divide by zero!
Division failed - using default value: 0

Input validation errors

Checking that user input meets expected criteria:

#include <iostream>

int getValidAge()
{
    int age;
    std::cout << "Enter your age (0-150): ";
    std::cin >> age;
    
    // Error detection: Check input range
    if (age < 0)
    {
        std::cout << "Error: Age cannot be negative!" << std::endl;
        return -1;  // Error code
    }
    
    if (age > 150)
    {
        std::cout << "Error: Age seems unrealistic (>150)!" << std::endl;
        return -1;  // Error code
    }
    
    return age;  // Valid age
}

std::string getValidGrade()
{
    std::string grade;
    std::cout << "Enter letter grade (A, B, C, D, F): ";
    std::cin >> grade;
    
    // Error detection: Check valid grade letters
    if (grade == "A" || grade == "B" || grade == "C" || grade == "D" || grade == "F")
    {
        return grade;  // Valid grade
    }
    
    std::cout << "Error: Invalid grade '" << grade << "'" << std::endl;
    return "";  // Error indicator (empty string)
}

int main()
{
    int age = getValidAge();
    if (age != -1)  // Check for error
    {
        std::cout << "Valid age entered: " << age << std::endl;
    }
    
    std::string grade = getValidGrade();
    if (!grade.empty())  // Check for error
    {
        std::cout << "Valid grade entered: " << grade << std::endl;
    }
    
    return 0;
}

Sample Output:

Enter your age (0-150): 25
Valid age entered: 25
Enter letter grade (A, B, C, D, F): X
Error: Invalid grade 'X'

Resource errors

Problems with files, memory, or other system resources:

#include <iostream>
#include <fstream>
#include <string>

bool readFileContent(const std::string& filename, std::string& content)
{
    std::ifstream file(filename);
    
    // Error detection: Check if file opened successfully
    if (!file.is_open())
    {
        std::cout << "Error: Could not open file '" << filename << "'" << std::endl;
        return false;
    }
    
    // Read file content
    std::string line;
    content.clear();
    
    while (std::getline(file, line))
    {
        content += line + "\n";
    }
    
    // Check for read errors
    if (file.bad())
    {
        std::cout << "Error: Failed to read from file '" << filename << "'" << std::endl;
        return false;
    }
    
    file.close();
    return true;  // Success
}

int main()
{
    std::string content;
    
    // Try to read a file that probably doesn't exist
    if (readFileContent("nonexistent.txt", content))
    {
        std::cout << "File content:\n" << content << std::endl;
    }
    else
    {
        std::cout << "Failed to read file - handling error gracefully" << std::endl;
    }
    
    return 0;
}

Output:

Error: Could not open file 'nonexistent.txt'
Failed to read file - handling error gracefully

Error handling strategies

Return error codes

Use special return values to indicate errors:

#include <iostream>
#include <vector>

// Return -1 for error, valid index otherwise
int findElement(const std::vector<int>& vec, int target)
{
    if (vec.empty())
    {
        std::cout << "Error: Cannot search empty vector" << std::endl;
        return -1;  // Error code
    }
    
    for (int i = 0; i < static_cast<int>(vec.size()); ++i)
    {
        if (vec[i] == target)
        {
            return i;  // Success: return index
        }
    }
    
    return -1;  // Not found (could also be separate from error)
}

double calculateSquareRoot(double number, bool& success)
{
    if (number < 0)
    {
        std::cout << "Error: Cannot calculate square root of negative number" << std::endl;
        success = false;
        return 0.0;  // Error value
    }
    
    success = true;
    return std::sqrt(number);  // Note: Need to include <cmath> in real code
}

int main()
{
    std::vector<int> numbers = {10, 20, 30, 40};
    
    int index = findElement(numbers, 25);
    if (index == -1)
    {
        std::cout << "Element not found or error occurred" << std::endl;
    }
    else
    {
        std::cout << "Element found at index: " << index << std::endl;
    }
    
    bool success;
    double result = calculateSquareRoot(-4.0, success);
    if (!success)
    {
        std::cout << "Square root calculation failed" << std::endl;
    }
    
    return 0;
}

Output:

Element not found or error occurred
Error: Cannot calculate square root of negative number
Square root calculation failed

Use output parameters

Pass variables by reference to return both results and error status:

#include <iostream>
#include <string>

enum class ParseResult
{
    Success,
    InvalidFormat,
    OutOfRange
};

ParseResult parseInteger(const std::string& input, int& result)
{
    // Check for empty string
    if (input.empty())
    {
        return ParseResult::InvalidFormat;
    }
    
    // Simple parsing (this is a simplified version)
    bool negative = false;
    std::string digits = input;
    
    if (input[0] == '-')
    {
        negative = true;
        digits = input.substr(1);
    }
    else if (input[0] == '+')
    {
        digits = input.substr(1);
    }
    
    // Check if all characters are digits
    for (char c : digits)
    {
        if (c < '0' || c > '9')
        {
            return ParseResult::InvalidFormat;
        }
    }
    
    // Convert to integer (simplified - doesn't handle overflow properly)
    int value = 0;
    for (char c : digits)
    {
        value = value * 10 + (c - '0');
    }
    
    if (negative)
        value = -value;
    
    result = value;
    return ParseResult::Success;
}

void demonstrateParsing()
{
    std::vector<std::string> testInputs = {"123", "-456", "abc", "12x34", ""};
    
    for (const std::string& input : testInputs)
    {
        int result;
        ParseResult status = parseInteger(input, result);
        
        std::cout << "Input: '" << input << "' -> ";
        
        switch (status)
        {
        case ParseResult::Success:
            std::cout << "Success, value: " << result << std::endl;
            break;
        case ParseResult::InvalidFormat:
            std::cout << "Invalid format" << std::endl;
            break;
        case ParseResult::OutOfRange:
            std::cout << "Out of range" << std::endl;
            break;
        }
    }
}

int main()
{
    demonstrateParsing();
    return 0;
}

Output:

Input: '123' -> Success, value: 123
Input: '-456' -> Success, value: -456
Input: 'abc' -> Invalid format
Input: '12x34' -> Invalid format
Input: '' -> Invalid format

Early returns for error handling

Exit functions early when errors are detected:

#include <iostream>
#include <vector>

double calculateAverage(const std::vector<int>& numbers)
{
    // Early return for empty vector
    if (numbers.empty())
    {
        std::cout << "Error: Cannot calculate average of empty vector" << std::endl;
        return 0.0;
    }
    
    // Early return for vector with only zeros (might cause confusion)
    bool allZeros = true;
    for (int num : numbers)
    {
        if (num != 0)
        {
            allZeros = false;
            break;
        }
    }
    
    if (allZeros)
    {
        std::cout << "Warning: All values are zero" << std::endl;
        return 0.0;  // Technically correct, but worth noting
    }
    
    // Calculate sum
    int sum = 0;
    for (int num : numbers)
    {
        sum += num;
    }
    
    // Return average
    return static_cast<double>(sum) / numbers.size();
}

void processGrades()
{
    std::vector<int> grades1 = {};                    // Empty
    std::vector<int> grades2 = {0, 0, 0};            // All zeros
    std::vector<int> grades3 = {85, 92, 78, 88, 95}; // Valid grades
    
    std::cout << "Class 1 average: " << calculateAverage(grades1) << std::endl;
    std::cout << "Class 2 average: " << calculateAverage(grades2) << std::endl;
    std::cout << "Class 3 average: " << calculateAverage(grades3) << std::endl;
}

int main()
{
    processGrades();
    return 0;
}

Output:

Error: Cannot calculate average of empty vector
Class 1 average: 0
Warning: All values are zero
Class 2 average: 0
Class 3 average: 87.6

Defensive programming

Validate all inputs

Check preconditions before processing:

#include <iostream>
#include <vector>
#include <string>

class BankAccount
{
private:
    double balance;
    std::string accountNumber;
    
public:
    BankAccount(const std::string& accNum, double initialBalance)
    {
        // Validate account number
        if (accNum.empty() || accNum.length() < 5)
        {
            std::cout << "Error: Invalid account number" << std::endl;
            accountNumber = "INVALID";
            balance = 0.0;
            return;
        }
        
        // Validate initial balance
        if (initialBalance < 0)
        {
            std::cout << "Error: Initial balance cannot be negative" << std::endl;
            accountNumber = accNum;
            balance = 0.0;
            return;
        }
        
        accountNumber = accNum;
        balance = initialBalance;
        std::cout << "Account created successfully" << std::endl;
    }
    
    bool withdraw(double amount)
    {
        // Validate account state
        if (accountNumber == "INVALID")
        {
            std::cout << "Error: Cannot operate on invalid account" << std::endl;
            return false;
        }
        
        // Validate withdrawal amount
        if (amount <= 0)
        {
            std::cout << "Error: Withdrawal amount must be positive" << std::endl;
            return false;
        }
        
        if (amount > balance)
        {
            std::cout << "Error: Insufficient funds" << std::endl;
            return false;
        }
        
        balance -= amount;
        std::cout << "Withdrawal successful. New balance: $" << balance << std::endl;
        return true;
    }
    
    bool deposit(double amount)
    {
        // Validate account state
        if (accountNumber == "INVALID")
        {
            std::cout << "Error: Cannot operate on invalid account" << std::endl;
            return false;
        }
        
        // Validate deposit amount
        if (amount <= 0)
        {
            std::cout << "Error: Deposit amount must be positive" << std::endl;
            return false;
        }
        
        balance += amount;
        std::cout << "Deposit successful. New balance: $" << balance << std::endl;
        return true;
    }
    
    double getBalance() const
    {
        if (accountNumber == "INVALID")
        {
            std::cout << "Error: Cannot get balance of invalid account" << std::endl;
            return -1;  // Error indicator
        }
        return balance;
    }
};

int main()
{
    // Test valid account
    BankAccount account1("12345", 1000.0);
    account1.deposit(200.0);
    account1.withdraw(150.0);
    
    std::cout << std::endl;
    
    // Test invalid account creation
    BankAccount account2("", -500.0);
    account2.deposit(100.0);  // Should fail
    
    std::cout << std::endl;
    
    // Test invalid operations
    account1.withdraw(-50.0);   // Negative amount
    account1.withdraw(2000.0);  // Insufficient funds
    
    return 0;
}

Output:

Account created successfully
Deposit successful. New balance: $1200
Withdrawal successful. New balance: $1050

Error: Invalid account number
Error: Initial balance cannot be negative
Account created successfully
Error: Cannot operate on invalid account

Error: Withdrawal amount must be positive
Error: Insufficient funds

Range checking for arrays and containers

Always verify indices are valid:

#include <iostream>
#include <vector>

class SafeVector
{
private:
    std::vector<int> data;
    
public:
    void push_back(int value)
    {
        data.push_back(value);
    }
    
    bool get(int index, int& value) const
    {
        // Validate index
        if (index < 0)
        {
            std::cout << "Error: Index cannot be negative (" << index << ")" << std::endl;
            return false;
        }
        
        if (index >= static_cast<int>(data.size()))
        {
            std::cout << "Error: Index " << index << " out of bounds (size: " << data.size() << ")" << std::endl;
            return false;
        }
        
        value = data[index];
        return true;
    }
    
    bool set(int index, int value)
    {
        // Validate index
        if (index < 0)
        {
            std::cout << "Error: Index cannot be negative (" << index << ")" << std::endl;
            return false;
        }
        
        if (index >= static_cast<int>(data.size()))
        {
            std::cout << "Error: Index " << index << " out of bounds (size: " << data.size() << ")" << std::endl;
            return false;
        }
        
        data[index] = value;
        return true;
    }
    
    int size() const
    {
        return static_cast<int>(data.size());
    }
    
    void printAll() const
    {
        std::cout << "Vector contents: ";
        for (int i = 0; i < static_cast<int>(data.size()); ++i)
        {
            std::cout << data[i] << " ";
        }
        std::cout << std::endl;
    }
};

int main()
{
    SafeVector vec;
    vec.push_back(10);
    vec.push_back(20);
    vec.push_back(30);
    
    vec.printAll();
    
    // Test valid access
    int value;
    if (vec.get(1, value))
    {
        std::cout << "vec[1] = " << value << std::endl;
    }
    
    // Test invalid access
    vec.get(-1, value);      // Negative index
    vec.get(5, value);       // Too large index
    vec.set(10, 999);        // Invalid set operation
    
    return 0;
}

Output:

Vector contents: 10 20 30 
vec[1] = 20
Error: Index cannot be negative (-1)
Error: Index 5 out of bounds (size: 3)
Error: Index 10 out of bounds (size: 3)

Error logging and debugging

Simple error logging

Keep track of errors for debugging:

#include <iostream>
#include <vector>
#include <string>

class ErrorLogger
{
private:
    std::vector<std::string> errorLog;
    
public:
    void logError(const std::string& function, const std::string& error)
    {
        std::string entry = "[" + function + "] " + error;
        errorLog.push_back(entry);
        std::cout << "ERROR: " << entry << std::endl;
    }
    
    void logWarning(const std::string& function, const std::string& warning)
    {
        std::string entry = "[" + function + "] WARNING: " + warning;
        errorLog.push_back(entry);
        std::cout << "WARNING: " << entry << std::endl;
    }
    
    void printErrorLog() const
    {
        std::cout << "\n=== Error Log ===" << std::endl;
        if (errorLog.empty())
        {
            std::cout << "No errors logged." << std::endl;
        }
        else
        {
            for (const std::string& entry : errorLog)
            {
                std::cout << entry << std::endl;
            }
        }
        std::cout << "=================" << std::endl;
    }
};

// Global error logger
ErrorLogger logger;

double safeDivision(double a, double b)
{
    if (b == 0.0)
    {
        logger.logError("safeDivision", "Division by zero attempted");
        return 0.0;
    }
    
    if (b > -0.0001 && b < 0.0001 && b != 0.0)
    {
        logger.logWarning("safeDivision", "Division by very small number: " + std::to_string(b));
    }
    
    return a / b;
}

int validateAndSum(const std::vector<int>& numbers)
{
    if (numbers.empty())
    {
        logger.logError("validateAndSum", "Empty vector provided");
        return 0;
    }
    
    int sum = 0;
    int negativeCount = 0;
    
    for (int num : numbers)
    {
        if (num < 0)
        {
            negativeCount++;
        }
        sum += num;
    }
    
    if (negativeCount > 0)
    {
        logger.logWarning("validateAndSum", 
            "Found " + std::to_string(negativeCount) + " negative numbers");
    }
    
    return sum;
}

int main()
{
    std::cout << "Testing error detection and logging:" << std::endl;
    
    // Test division
    std::cout << "10 / 2 = " << safeDivision(10.0, 2.0) << std::endl;
    std::cout << "10 / 0 = " << safeDivision(10.0, 0.0) << std::endl;
    std::cout << "10 / 0.00001 = " << safeDivision(10.0, 0.00001) << std::endl;
    
    // Test sum validation
    std::vector<int> numbers1 = {1, 2, 3, 4, 5};
    std::vector<int> numbers2 = {};
    std::vector<int> numbers3 = {1, -2, 3, -4, 5};
    
    std::cout << "Sum 1: " << validateAndSum(numbers1) << std::endl;
    std::cout << "Sum 2: " << validateAndSum(numbers2) << std::endl;
    std::cout << "Sum 3: " << validateAndSum(numbers3) << std::endl;
    
    // Print error log
    logger.printErrorLog();
    
    return 0;
}

Output:

Testing error detection and logging:
10 / 2 = 5
ERROR: [safeDivision] Division by zero attempted
10 / 0 = 0
WARNING: [safeDivision] WARNING: Division by very small number: 0.000010
10 / 0.00001 = 1e+06
Sum 1: 15
ERROR: [validateAndSum] Empty vector provided
Sum 2: 0
WARNING: [validateAndSum] WARNING: Found 2 negative numbers
Sum 3: 3

=== Error Log ===
[safeDivision] Division by zero attempted
[safeDivision] WARNING: Division by very small number: 0.000010
[validateAndSum] Empty vector provided
[validateAndSum] WARNING: Found 2 negative numbers
=================

Error handling best practices

1. Fail fast

Detect and report errors as soon as possible:

#include <iostream>

class Rectangle
{
private:
    double width, height;
    
public:
    Rectangle(double w, double h)
    {
        // Fail fast: Check parameters immediately
        if (w <= 0 || h <= 0)
        {
            std::cout << "Error: Rectangle dimensions must be positive" << std::endl;
            width = 1.0;  // Default to safe values
            height = 1.0;
        }
        else
        {
            width = w;
            height = h;
        }
    }
    
    double area() const
    {
        // No need to check here - constructor ensures valid state
        return width * height;
    }
    
    void resize(double w, double h)
    {
        // Fail fast: Validate before changing state
        if (w <= 0 || h <= 0)
        {
            std::cout << "Error: Cannot resize to non-positive dimensions" << std::endl;
            return;  // Don't change the object state
        }
        
        width = w;
        height = h;
    }
};

2. Use clear error messages

Make error messages helpful for debugging:

#include <iostream>
#include <string>

bool validatePassword(const std::string& password, std::string& errorMessage)
{
    if (password.length() < 8)
    {
        errorMessage = "Password too short (minimum 8 characters, got " + 
                      std::to_string(password.length()) + ")";
        return false;
    }
    
    if (password.length() > 50)
    {
        errorMessage = "Password too long (maximum 50 characters, got " + 
                      std::to_string(password.length()) + ")";
        return false;
    }
    
    bool hasDigit = false;
    bool hasUpper = false;
    bool hasLower = false;
    
    for (char c : password)
    {
        if (c >= '0' && c <= '9') hasDigit = true;
        if (c >= 'A' && c <= 'Z') hasUpper = true;
        if (c >= 'a' && c <= 'z') hasLower = true;
    }
    
    if (!hasDigit)
    {
        errorMessage = "Password must contain at least one digit";
        return false;
    }
    
    if (!hasUpper)
    {
        errorMessage = "Password must contain at least one uppercase letter";
        return false;
    }
    
    if (!hasLower)
    {
        errorMessage = "Password must contain at least one lowercase letter";
        return false;
    }
    
    return true;  // Password is valid
}

int main()
{
    std::vector<std::string> testPasswords = {
        "abc",           // Too short
        "abcdefgh",      // No digits or uppercase
        "ABCDEFGH1",     // No lowercase
        "abcdefgh1",     // No uppercase  
        "Password123"    // Valid
    };
    
    for (const std::string& pwd : testPasswords)
    {
        std::string error;
        if (validatePassword(pwd, error))
        {
            std::cout << "'" << pwd << "' is valid" << std::endl;
        }
        else
        {
            std::cout << "'" << pwd << "' is invalid: " << error << std::endl;
        }
    }
    
    return 0;
}

Output:

'abc' is invalid: Password too short (minimum 8 characters, got 3)
'abcdefgh' is invalid: Password must contain at least one digit
'ABCDEFGH1' is invalid: Password must contain at least one lowercase letter
'abcdefgh1' is invalid: Password must contain at least one uppercase letter
'Password123' is valid

3. Provide recovery mechanisms

When possible, allow programs to recover from errors:

#include <iostream>
#include <vector>

class RobustCalculator
{
public:
    static double average(const std::vector<double>& numbers)
    {
        if (numbers.empty())
        {
            std::cout << "Warning: Empty vector, returning 0" << std::endl;
            return 0.0;  // Reasonable default
        }
        
        double sum = 0.0;
        int validCount = 0;
        
        for (double num : numbers)
        {
            // Skip invalid numbers (NaN, infinity)
            if (std::isnan(num) || std::isinf(num))
            {
                std::cout << "Warning: Skipping invalid number in average calculation" << std::endl;
                continue;
            }
            
            sum += num;
            validCount++;
        }
        
        if (validCount == 0)
        {
            std::cout << "Error: No valid numbers to average, returning 0" << std::endl;
            return 0.0;
        }
        
        return sum / validCount;
    }
};

int main()
{
    std::vector<double> numbers1 = {1.0, 2.0, 3.0, 4.0, 5.0};
    std::vector<double> numbers2 = {};
    std::vector<double> numbers3 = {1.0, std::numeric_limits<double>::quiet_NaN(), 3.0};
    
    std::cout << "Average 1: " << RobustCalculator::average(numbers1) << std::endl;
    std::cout << "Average 2: " << RobustCalculator::average(numbers2) << std::endl;
    std::cout << "Average 3: " << RobustCalculator::average(numbers3) << std::endl;
    
    return 0;
}

Summary

Error detection and handling are essential for creating robust programs:

Error detection strategies:

  • Validate all inputs before processing
  • Check preconditions and postconditions
  • Monitor resource operations (files, memory)
  • Use range checking for arrays and containers

Error handling approaches:

  • Return error codes or status values
  • Use output parameters to return both results and status
  • Exit functions early when errors occur
  • Log errors for debugging and monitoring

Best practices:

  • Fail fast - detect errors as early as possible
  • Provide clear, actionable error messages
  • Allow recovery when feasible
  • Validate inputs at function boundaries
  • Use defensive programming techniques

Good error handling makes your programs more reliable, easier to debug, and more user-friendly. It's an investment in code quality that pays dividends when problems arise.

Quiz

  1. What's the difference between error detection and error handling?
  2. Why is it important to validate function parameters?
  3. What are some ways to indicate that a function encountered an error?
  4. What does "fail fast" mean in error handling?
  5. Why should error messages be specific and informative?

Practice exercises

Add error detection and handling to these functions:

  1. Write a safe array access function that checks bounds
  2. Create a robust string-to-integer conversion function
  3. Implement a division function that handles all error cases
  4. Design a file reader that handles missing files and read errors

Continue Learning

Explore other available lessons while this one is being prepared.

View Course

Explore More Courses

Discover other available courses while this lesson is being prepared.

Browse Courses

Lesson Discussion

Share your thoughts and questions

💬

No comments yet. Be the first to share your thoughts!

Sign in to join the discussion