Higher-Order Functions

📖
Definition

Higher-Order Function: A function that either takes another function as a parameter OR returns a function as a result.

Why Do We Need Them?

Imagine you have a list of numbers and you want to:

  • Keep only the even numbers
  • Keep only numbers greater than 10
  • Keep only numbers divisible by 3

You could write three different functions... or write ONE function that accepts different "rules" as parameters!

💡
Key Idea

Separate what to do (iterate through a list) from how to decide (the specific rule)


Worked Example: Filtering Numbers

Step 1: The Problem

We have a list of numbers: [3, 8, 15, 4, 22, 7, 11]

We want to filter them based on different conditions.

Step 2: Without Higher-Order Functions (Repetitive)

# Filter for even numbers
def filter_even(numbers):
    result = []
    for num in numbers:
        if num % 2 == 0:
            result.append(num)
    return result

# Filter for numbers > 10
def filter_large(numbers):
    result = []
    for num in numbers:
        if num > 10:
            result.append(num)
    return result
⚠️
Problem

Notice how we're repeating the same loop structure? Only the condition changes!

Step 3: With Higher-Order Function (Smart)

def filter_numbers(numbers, condition):
    """
    Filter numbers based on any condition function.
    
    numbers: list of numbers
    condition: a function that returns True/False
    """
    result = []
    for num in numbers:
        if condition(num):  # Call the function we received!
            result.append(num)
    return result
Solution

Now we have ONE function that can work with ANY condition!

Step 4: Define Simple Condition Functions

def is_even(n):
    return n % 2 == 0

def is_large(n):
    return n > 10

def is_small(n):
    return n < 10

Step 5: Use It!

numbers = [3, 8, 15, 4, 22, 7, 11]

print(filter_numbers(numbers, is_even))   # [8, 4, 22]
print(filter_numbers(numbers, is_large))  # [15, 22, 11]
print(filter_numbers(numbers, is_small))  # [3, 8, 4, 7]
ℹ️
Notice

We pass the function name WITHOUT parentheses: is_even not is_even()


Practice Exercises

💻
Exercise 1: String Filter

Complete this function:

def filter_words(words, condition):
    # Your code here
    pass

def is_long(word):
    return len(word) > 5

def starts_with_a(word):
    return word.lower().startswith('a')

# Test it:
words = ["apple", "cat", "banana", "amazing", "dog"]
print(filter_words(words, is_long))         # Should print: ["banana", "amazing"]
print(filter_words(words, starts_with_a))   # Should print: ["apple", "amazing"]
💻
Exercise 2: Number Transformer

Write a higher-order function that transforms numbers:

def transform_numbers(numbers, transformer):
    # Your code here: apply transformer to each number
    pass

def double(n):
    return n * 2

def square(n):
    return n ** 2

# Test it:
nums = [1, 2, 3, 4, 5]
print(transform_numbers(nums, double))   # Should print: [2, 4, 6, 8, 10]
print(transform_numbers(nums, square))   # Should print: [1, 4, 9, 16, 25]
💻
Exercise 3: Grade Calculator

Create a function that grades scores using different grading systems:

def apply_grading(scores, grade_function):
    # Your code here
    pass

def strict_grade(score):
    if score >= 90:
        return 'A'
    elif score >= 80:
        return 'B'
    else:
        return 'C'

def pass_fail(score):
    return 'Pass' if score >= 60 else 'Fail'

# Test it:
scores = [95, 75, 85, 55]
print(apply_grading(scores, strict_grade))  # Should print: ['A', 'C', 'B', 'C']
print(apply_grading(scores, pass_fail))     # Should print: ['Pass', 'Pass', 'Pass', 'Fail']

Conclusion

📝
Remember

1. Functions can be passed as parameters (like any other value)
2. The higher-order function provides the structure (loop, collection)
3. The parameter function provides the specific behavior (condition, transformation)
4. This makes code more reusable and flexible

💡
Real Python Examples

Python has built-in higher-order functions you'll use all the time:
sorted(items, key=function)
map(function, items)
filter(function, items)


Challenge Exercise

DNA Sequence Validator

Write a higher-order function validate_sequences(sequences, validator) that checks a list of DNA sequences using different validation rules.

Validation functions to create:

  • is_valid_dna(seq) - checks if sequence contains only A, C, G, T
  • is_long_enough(seq) - checks if sequence is at least 10 characters
  • has_start_codon(seq) - checks if sequence starts with "ATG"
sequences = ["ATGCGATCG", "ATGXYZ", "AT", "ATGCCCCCCCCCC"]

# Your solution should work like this:
print(validate_sequences(sequences, is_valid_dna))
# [True, False, True, True]

print(validate_sequences(sequences, is_long_enough))
# [False, False, False, True]