Table of contents
Implement code functionality

How to write an algorithm in Python

May 30, 2025
 ・ by  
Claude and the Anthropic Team
Table of contents
H2 Link Template
Try Claude

Python algorithms transform complex problems into efficient, step-by-step solutions. Whether you're sorting data, searching through lists, or optimizing performance, understanding algorithm fundamentals helps you write better code and solve problems systematically.

This guide covers essential techniques, practical tips, and real-world applications for writing robust algorithms, with code examples created using Claude, an AI assistant built by Anthropic.

Creating a simple algorithm with a function

def calculate_average(numbers):
    total = sum(numbers)
    return total / len(numbers)

result = calculate_average([10, 20, 30, 40, 50])
print(f"The average is: {result}")
The average is: 30.0

The calculate_average function demonstrates a fundamental algorithmic pattern: breaking down a complex operation into smaller, reusable steps. This approach transforms raw data into meaningful insights through systematic processing.

The algorithm follows key principles that make it both efficient and maintainable:

  • Single responsibility: The function handles one specific task
  • Input validation: It expects a list of numbers as input
  • Clear output: It returns a calculated average without side effects

While this example may seem straightforward, it establishes essential practices for building more sophisticated algorithms. The function's structure creates a reusable template that developers can adapt for handling various numerical computations.

Basic algorithm design patterns

Building on these foundational patterns, Python's control structures and modular design enable you to create robust, production-ready algorithms that handle edge cases gracefully.

Using control structures in algorithms

def find_maximum(numbers):
    if not numbers:
        return None
    max_value = numbers[0]
    for num in numbers:
        if num > max_value:
            max_value = num
    return max_value

print(find_maximum([5, 12, 9, 42, 17]))
42

The find_maximum function demonstrates essential control structures that form the backbone of algorithmic thinking. It combines conditional logic and iteration to systematically process data and handle edge cases.

  • The initial if not numbers check prevents errors by returning None for empty lists
  • Setting max_value to the first element creates a baseline for comparison
  • The for loop systematically compares each number to find the highest value
  • A simple if statement updates max_value when a larger number is found

This pattern showcases how combining basic control structures creates robust algorithms that handle varying inputs reliably. The function maintains efficiency by making just one pass through the data while tracking the maximum value.

Adding input validation to your calculate_discount() algorithm

def calculate_discount(price, discount_percent):
    if not isinstance(price, (int, float)) or price < 0:
        raise ValueError("Price must be a positive number")
    if not 0 <= discount_percent <= 100:
        raise ValueError("Discount must be between 0 and 100")
    
    return price * (1 - discount_percent / 100)

print(calculate_discount(100, 20))
80.0

Input validation transforms this discount calculator into a robust, production-ready function. The code systematically checks for invalid inputs before processing any calculations.

  • The first validation uses isinstance() to ensure price is a number and verifies it's not negative
  • The second check confirms discount_percent falls between 0 and 100
  • Both checks raise clear error messages that help developers quickly identify and fix issues

This defensive programming approach prevents subtle bugs that could arise from invalid inputs. The function returns the discounted price only after confirming all parameters meet the required criteria.

Creating modular algorithms with helper functions

def is_prime(n):
    if n <= 1:
        return False
    for i in range(2, int(n**0.5) + 1):
        if n % i == 0:
            return False
    return True

def get_primes_below(limit):
    return [num for num in range(2, limit) if is_prime(num)]

print(get_primes_below(20))
[2, 3, 5, 7, 11, 13, 17, 19]

This code demonstrates how breaking algorithms into focused helper functions improves readability and reusability. The is_prime() function handles a single task: determining if a number is prime. The get_primes_below() function then uses this helper to generate a list of prime numbers.

  • The is_prime() function efficiently checks divisibility up to the square root of the input number
  • Using is_prime() as a helper makes the code more maintainable and easier to test
  • The list comprehension in get_primes_below() shows how modular functions enable cleaner, more expressive code

This modular approach creates building blocks you can combine to solve more complex problems. Each function has a clear, single purpose that makes debugging and modifications straightforward.

Advanced algorithm techniques

Building on these modular foundations, Python offers powerful techniques to optimize algorithm performance through binary search, decorators, and recursive memoization patterns.

Implementing binary_search() with optimized time complexity

def binary_search(sorted_list, target):
    left, right = 0, len(sorted_list) - 1
    
    while left <= right:
        mid = (left + right) // 2
        if sorted_list[mid] == target:
            return mid
        elif sorted_list[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
            
    return -1  # Not found

print(binary_search([1, 3, 5, 7, 9, 11], 5))
2

Binary search dramatically speeds up finding items in sorted lists by systematically eliminating half the remaining search space with each comparison. The algorithm maintains two pointers, left and right, to track the current search boundaries.

  • The mid calculation finds the center point between left and right pointers
  • If the middle value matches the target, we return its index
  • When the middle value is too small, we search the right half by moving left
  • When it's too large, we search the left half by moving right

This divide-and-conquer approach achieves O(log n) time complexity. The algorithm returns -1 if the target isn't found after exhausting all possible positions.

Using decorators to enhance algorithm functionality

import time

def timing_decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Function {func.__name__} took {end_time - start_time:.6f} seconds")
        return result
    return wrapper

@timing_decorator
def sort_algorithm(arr):
    return sorted(arr)

sort_algorithm([5, 2, 9, 1, 5, 6])
Function sort_algorithm took 0.000058 seconds
[1, 2, 5, 5, 6, 9]

Decorators transform how functions behave without changing their internal code. The timing_decorator wraps around functions to measure their execution time. When applied to sort_algorithm, it automatically tracks how long the sorting process takes.

  • The wrapper function captures the start and end times using time.time()
  • It executes the original function between these time measurements
  • The decorator prints the elapsed time and returns the function's result

This pattern enables performance monitoring across multiple functions by simply adding the @timing_decorator annotation. You can apply the same decorator to any function where you need to track execution time. The approach demonstrates how Python's decorator syntax elegantly extends function behavior without cluttering the main logic.

Writing efficient recursive algorithms with memoization

def fibonacci(n, memo={}):
    if n in memo:
        return memo[n]
    if n <= 1:
        return n
    memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo)
    return memo[n]

for i in range(10):
    print(fibonacci(i), end=" ")
0 1 1 2 3 5 8 13 21 34

The fibonacci function uses memoization to dramatically speed up recursive calculations. This technique stores previously computed results in the memo dictionary, preventing redundant calculations of the same Fibonacci numbers.

  • The function first checks if we've already calculated the result for n using if n in memo
  • Base cases handle inputs of 0 and 1 directly
  • For larger numbers, the function stores each new calculation in memo before returning it

Without memoization, calculating the 100th Fibonacci number would require billions of recursive calls. With memoization, the function needs only 99 calculations. This transforms an exponential time complexity into a linear one, making the algorithm significantly more efficient.

Get unstuck faster with Claude

Claude is an AI assistant created by Anthropic that excels at helping developers write better code. It combines deep technical knowledge with natural conversation to guide you through complex programming challenges.

When you encounter tricky algorithm problems or need help optimizing your code, Claude steps in as your AI mentor. It can explain concepts like binary search complexity, suggest improvements to your recursive functions, or help debug that stubborn edge case in your sorting algorithm.

Start accelerating your Python development today. Sign up for free at Claude.ai and transform how you write algorithms with personalized, expert guidance.

Some real-world applications

Building on these foundational techniques, Python algorithms solve real business challenges through practical applications like text analysis and data processing pipelines.

Using algorithms for text analysis with count_word_frequency()

The count_word_frequency() function transforms raw text into a structured dictionary that maps each word to its number of occurrences, enabling efficient analysis of word patterns and language usage.

def count_word_frequency(text):
    words = text.lower().split()
    frequency = {}
    for word in words:
        frequency[word] = frequency.get(word, 0) + 1
    return frequency

sample_text = "The quick brown fox jumps over the lazy dog"
print(count_word_frequency(sample_text))

The count_word_frequency function efficiently tracks how often each word appears in a text. It first converts the input text to lowercase and splits it into individual words. The function then creates an empty dictionary to store word counts.

For each word in the text, the function uses the dictionary's get() method to either retrieve the current count or return 0 if the word is new. It adds 1 to this count and stores the updated value. This elegant approach handles both new and existing words in a single line.

  • Lowercase conversion ensures "The" and "the" count as the same word
  • The split() method automatically handles multiple spaces between words
  • Dictionary lookups provide fast O(1) access to word counts

Implementing a filter_and_analyze() function for data processing

The filter_and_analyze() function combines data filtering and statistical analysis to transform raw numerical data into meaningful insights through a clean dictionary interface.

def filter_and_analyze(data, threshold):
    if not isinstance(threshold, (int, float)):
        raise ValueError("Threshold must be a number")
    
    filtered_data = [x for x in data if x > threshold]
    
    if not filtered_data:
        return {"count": 0, "average": None, "max": None}
    
    return {
        "count": len(filtered_data),
        "average": sum(filtered_data) / len(filtered_data),
        "max": max(filtered_data)
    }

sensor_readings = [23.1, 19.8, 31.4, 18.9, 27.5, 22.2]
print(filter_and_analyze(sensor_readings, 25))

The filter_and_analyze function processes numerical data and returns statistical insights in a dictionary format. It first validates that the threshold parameter is actually a number. Using a list comprehension, it creates filtered_data containing only values above the specified threshold.

  • If no values pass the threshold filter, it returns a dictionary with zero count and None values
  • For valid filtered data, it calculates three metrics: count of values, their average, and the maximum value

When run with sensor_readings and a threshold of 25, the function returns statistics only for readings above 25—making it useful for scenarios like monitoring sensor data that exceeds specific thresholds.

Common errors and challenges

Python algorithms can fail in subtle ways when handling edge cases like empty lists, infinite recursion, and mutable default arguments. Understanding these pitfalls helps you write more robust code.

Handling empty lists in the calculate_average() function

The calculate_average() function fails when processing empty lists. This common edge case triggers a ZeroDivisionError because the function attempts to divide by zero. A robust implementation must handle empty inputs gracefully.

def calculate_average(numbers):
    total = sum(numbers)
    return total / len(numbers)
    
print(calculate_average([]))

When calculate_average() receives an empty list, Python attempts to divide the sum (0) by the length (0). This triggers a ZeroDivisionError. The code below demonstrates a more resilient approach to this challenge.

def calculate_average(numbers):
    if not numbers:
        return 0
    total = sum(numbers)
    return total / len(numbers)
    
print(calculate_average([]))

The improved calculate_average() function adds a crucial validation check using if not numbers to handle empty lists. This prevents the ZeroDivisionError by returning 0 when the input list contains no elements.

  • Always validate inputs before performing calculations that could trigger division by zero
  • Consider whether returning 0 or raising an exception makes more sense for your use case
  • Watch for similar edge cases when working with other mathematical operations like finding averages, percentages, or ratios

This pattern applies broadly to functions that compute statistics or perform calculations on collections. Empty or null inputs often require special handling to prevent runtime errors.

Preventing infinite recursion in the factorial() function

The factorial() function calculates the product of all integers from 1 to n. Without proper base conditions, recursive functions can call themselves indefinitely. This leads to stack overflow errors that crash your program. The code below demonstrates this common pitfall.

def factorial(n):
    return n * factorial(n-1)

print(factorial(5))

The factorial() function keeps calling itself with n-1 without a stopping condition. Each recursive call adds to the program's memory stack until it runs out of space. The corrected implementation below adds the necessary base case.

def factorial(n):
    if n <= 1:
        return 1
    return n * factorial(n-1)

print(factorial(5))

The improved factorial() function adds a crucial base case that stops the recursion when n reaches 1 or less. This prevents infinite recursion by providing a clear exit condition. The function now safely calculates factorials by multiplying n by the factorial of n-1 until it hits the base case.

  • Watch for missing base cases in any recursive function
  • Test edge cases like 0 and negative numbers
  • Consider using iteration instead of recursion for large inputs to avoid stack overflow

This pattern applies to all recursive algorithms. Always ensure your recursive functions have well-defined stopping conditions before they start calling themselves.

Avoiding bugs with mutable default arguments in the append_to_list() function

Python's mutable default arguments create a subtle trap that catches many developers. The append_to_list() function demonstrates how using an empty list as a default parameter leads to unexpected behavior when the function runs multiple times.

def append_to_list(item, my_list=[]):
    my_list.append(item)
    return my_list

print(append_to_list(1))
print(append_to_list(2))

Python creates the default my_list parameter only once when defining the function. Each subsequent call modifies the same list object instead of creating a fresh empty list. The code below demonstrates the proper implementation.

def append_to_list(item, my_list=None):
    if my_list is None:
        my_list = []
    my_list.append(item)
    return my_list

print(append_to_list(1))
print(append_to_list(2))

The improved append_to_list() function uses None as the default argument instead of an empty list. This prevents the common pitfall where Python reuses the same mutable list across multiple function calls. Setting my_list=None and creating a new list inside the function ensures each call starts fresh.

  • Watch for mutable defaults in function parameters including lists, dictionaries, and sets
  • Use None as the default argument when working with mutable data structures
  • Create new mutable objects inside the function body instead of the parameter list

This pattern applies whenever you need optional mutable arguments in your functions. The fix ensures predictable behavior by isolating each function call's data.

Learning or leveling up? Use Claude

Anthropic's Claude combines sophisticated code understanding with intuitive teaching abilities to guide you through Python algorithm development. This AI assistant analyzes your code structure, suggests optimizations, and helps you master advanced concepts through interactive problem-solving.

Here are some example prompts to start learning with Claude:

  • Debug recursive functions: Ask "Why does my factorial function cause a stack overflow?" and Claude will explain common recursion pitfalls and help implement proper base cases
  • Optimize algorithms: Ask "How can I make this binary search more efficient?" and Claude will suggest improvements while explaining the reasoning behind each optimization
  • Handle edge cases: Ask "What edge cases should I consider for my sorting algorithm?" and Claude will identify potential issues and demonstrate robust error handling
  • Improve code structure: Ask "How can I make this word frequency counter more maintainable?" and Claude will recommend modular design patterns and clean code practices

Experience personalized algorithm development guidance by signing up for free at Claude.ai.

For seamless integration into your development workflow, Claude Code brings AI assistance directly to your terminal, enabling real-time algorithm optimization and code review as you work.

FAQs

Additional Resources

How to round down in Python

2025-05-30
14 min
 read
Read more

How to create a class in Python

2025-05-22
14 min
 read
Read more

How to round up in Python

2025-05-22
14 min
 read
Read more

Leading companies build with Claude

ReplitCognitionGithub CopilotCursorSourcegraph
Try Claude
Get API Access
Copy
Expand