We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.

What Is Functional Programming in Python? A Complete Guide

Boot.dev Team
Boot.dev TeamProgramming course authors and video producers

Last published

Table of Contents

Functional programming is a style of programming where we compose functions instead of mutating state (updating the values of variables). While Python is best known for object-oriented programming, it also fully supports functional concepts like first-class functions, immutability, and pure functions. Learning to think functionally is one of the fastest ways to write cleaner, more maintainable code, no matter which language you end up working in.

All the content from our Boot.dev courses are available for free here on the blog. This one is the "What Is Functional Programming?" chapter of Learn Functional Programming in Python. If you want to try the far more immersive version of the course, do check it out!

What Is Functional Programming?

Functional programming is more about declaring what you want to happen, rather than how you want it to happen. Imperative (or procedural) programming declares both the what and the how.

Example of imperative code:

car = create_car()
car.add_gas(10)
car.clean_windows()

Example of functional code:

return clean_windows(add_gas(create_car()))

The important distinction is that in the functional example, we never change the value of the car variable, we just compose functions that return new values, with the outermost function, clean_windows in this case, returning the final result.

Why Use Python for Functional Programming?

Frankly, Python is not the best language for functional programming. Reasons include:

  1. No enforced static typing (though type hints are supported).
  2. (Almost) everything is mutable.
  3. No tail call optimization.
  4. Side effects are common.
  5. Imperative and OOP styles abound in popular libraries.
  6. Purity is not enforced (and sometimes not even encouraged).
  7. Sum types are hard to define.
  8. Pattern matching is weak at best.

So seriously, why are we using Python? One reason trumps all others: you already know Python. Python is a great choice for learning programming basics, OOP, algorithms, and data structures, and we don't think the tradeoff of learning a new language is worth it.

We'll still be able to cover the most important concepts of functional programming in Python, even if we have to jump through a hoop or two to do it. Things like:

  • Higher-order functions
  • First-class functions
  • Pure functions
  • Recursion
  • Closures
  • Currying

What Is Immutability in Python?

In functional programming, we strive to make data immutable. Once a value is created, it cannot be changed. Mutable data, on the other hand, can be changed after it's created.

Immutable data is easier to think about and work with. When 10 different functions have access to the same variable, and you're debugging a problem with that variable, you have to consider the possibility that any of those functions could have changed the value.

When a variable is immutable, you can be sure that it hasn't changed since it was created. It's a helluva lot easier to work with.

Generally speaking, immutability means fewer bugs and more maintainable code.

Tuples and lists are both ordered collections of values, but tuples are immutable and lists are mutable.

# lists are mutable
ages = [16, 21, 30]
ages.append(80)
print(ages)  # [16, 21, 30, 80]

# tuples are immutable
ages = (16, 21, 30)
more_ages = (80,)
all_ages = ages + more_ages
print(all_ages)  # (16, 21, 30, 80)

You can append to a list, but you can not append to a tuple. You can create a new copy of a tuple using values from an existing tuple, but you can't change the existing tuple.

How Does Declarative Code Compare to Imperative Code?

Click to play video

Functional programming tends to be popular among developers with a strong mathematical background. After all, a math equation isn't procedural – it's declarative. Take the following equation:

avg = Σx/N

To put this calculation in plain English:

  1. Σ is just the Greek letter Sigma, and it represents "the sum of a collection."
  2. x is the collection of numbers we're averaging.
  3. N is the number of elements in the collection.
  4. avg is equal to the sum of all the numbers in collection x divided by the number of elements in collection x.

So, the equation really just says that avg is the average of all the numbers in collection x. This math equation is a declarative way of writing "calculate the average of a list of numbers." Here's some imperative Python code that does the same thing:

def get_average(nums):
    total = 0
    for num in nums:
        total += num
    return total / len(nums)

However, with functional programming, we would write code that's a bit more declarative:

def get_average(nums):
    return sum(nums) / len(nums)

Here we're not keeping track of state (the total variable in the first example is "stateful"). We're simply composing functions together to get the result we want.

Should You Use Classes or Functions?

I run into new developers who, after learning about classes, want to use them everywhere. They assume that because they learned about functions first, functions are somehow inferior.

Nope. They're just different.

Here's my rule of thumb:

If you're unsure, default to functions. I find myself reaching for classes when I need something long-lived and stateful that would be easier to model if I could share behavior and data structure via inheritance. This is often the case for video games, simulations, and GUIs.

The difference is:

  • Classes encourage you to think about the world as a hierarchical collection of objects. Objects bundle behavior, data, and state together in a way that draws boundaries between instances of things, like chess pieces on a board.
  • Functions encourage you to think about the world as a series of data transformations. Functions take data as input and return a transformed output. For example, a function might take the entire state of a chess board and a move as inputs, and return the new state of the board as output.

Use what feels right to you in your projects, and adjust and refactor as you improve your skills.

How Does Functional Programming Compare to OOP?

Functional programming and object-oriented programming are styles for writing code. One isn't inherently superior to the other, but to be a well-rounded developer you should understand both well and use ideas from each when appropriate.

You'll encounter developers who love functional programming and others who love object-oriented programming. However, contrary to popular opinion, FP and OOP are not always at odds with one another. They aren't opposites. Of the four pillars of OOP, inheritance is the only one that doesn't fit with functional programming.

Inheritance isn't seen in functional code due to the mutable classes that come along with it. Encapsulation, polymorphism and abstraction are still used all the time in functional programming.

When working in a language that supports ideas from both FP and OOP (like Python, JavaScript, or Go) the best developers are the ones who can use the best ideas from both paradigms effectively and appropriately.

What's the Difference Between Statements and Expressions?

Studying functional programming is really about returning to the most basic aspects of programming and looking at them in a new way. Statements and expressions are a great example.

"Statements" are actions to be carried out. For example:

  • "Set n to 7"
  • "Define a function named greet"
  • "If x > 10, print a greeting to Alice"

In Python, such statements look like this:

n = 7  # Variable assignment statement

def greet(name):  # Function definition statement
    return f"Hello, {name}!"

if x > 10:  # `if` statement
    print(greet("Alice"))

for i in range(n):  # `for` loop statement
    print(i)

Every complete instruction is a statement.

Expressions are a subset of statements that produce values. Evaluating an expression results in a value that can be used in whatever way is needed. It can be assigned to a variable, returned from a function, etc.

result = 2 + 2  # Arithmetic expression
length = len("hello")  # Function call expression

One thing that may surprise you is that, in most languages (including Python), every function call is an expression. When you call a function, it returns a value – whether or not you realize it or do anything with that value. Even if a Python function doesn't have a return statement, it still implicitly returns None.

Because expressions always produce values, they're reusable and declarative. You can compose expressions and nest them within each other – but you can't always do that with other kinds of statements. Functional programming encourages the use of expressions over statements where possible, because expressions tend to minimize side effects, and make the code easier to reason about.

How Do Ternary Expressions Work in Python?

Ternaries are a great way to reduce a series of statements, like an if/else block, to a single expression. When you first learned how to use conditional logic in Python, it probably looked like this:

result = 0
if number % 2 == 0:
    result = number / 2
else:
    result = (number * 3) + 1

This code sets result to a dummy value like 0 (None would also work), then overwrites it with its "real" value based on the condition. A ternary lets us do all that in one expression:

result = number / 2 if number % 2 == 0 else (number * 3) + 1

Note that we also avoided mutating the result variable! Ternary expressions are good for maintaining immutability.

The syntax for a ternary in Python is value_a if condition else value_b. This qualifies as an expression because it's a single statement that evaluates to a value – one of two values, depending on the condition.

Ternary expressions are cool, but don't overdo it. If you're dealing with complex conditional logic, it's often easier to work with full if/else blocks than to try to nest ternaries inside each other.

How Do You Debug Functional Code?

It's nearly impossible, even for tenured senior developers, to write perfect code the first time. That's why debugging is such an important skill. The trouble is, sometimes you have these "elegant" (sarcasm intended) one-liners that are tricky to debug:

def get_player_position(position, velocity, friction, gravity):
    return calc_gravity(calc_friction(calc_move(position, velocity), friction), gravity)

If the output of get_player_position is incorrect, it's hard to know what's going on inside that black box. Break it up! Then you can inspect the moved, slowed, and final variables more easily:

def get_player_position(position, velocity, friction, gravity):
    moved = calc_move(position, velocity)
    slowed = calc_friction(moved, friction)
    final = calc_gravity(slowed, gravity)
    print(f"moved: {moved}, slowed: {slowed}, final: {final}")
    return final

Once you've run it, found the issue, and solved it, you can remove the print() statements. From here, you can dig into the building blocks of FP: higher-order functions and pure functions.

Frequently Asked Questions

Is Python a functional programming language?

No, Python is a multi-paradigm language. While it isn't a pure functional language like Haskell or Elixir, it fully supports functional programming concepts like first-class functions, higher-order functions, pure functions, and closures.

What is an example of functional programming in Python?

Using the built-in sum() function on a list instead of looping over it with a for loop and adding to a counter variable is a simple example. You declare what you want (the sum) instead of the exact steps to compute it.

What is the difference between declarative and imperative programming?

Declarative programming focuses on what you want to happen, while imperative programming spells out both what should happen and the step-by-step how. Functional programming leans declarative.

Should I use classes or functions in Python?

If you're unsure, default to functions. Reach for classes when you need long-lived, stateful objects where behavior and data structure are tightly coupled, like in game development, simulations, or GUIs.