Python and the Magic of First-Class Functions


When people hear “first-class functions,” it can sound intimidating, like some high-level computer science buzzword. But in Python, the idea is surprisingly simple and incredibly powerful: functions are just like any other value.
You can pass them around, return them, tuck them into a dictionary, or hand them over to another function. Once you start using this feature, you’ll see Python in a new light.
Let’s take a journey through the key ideas, with examples that stick.
Functions as values
def greet(name):
return f"Hello, {name}!"
# Store it in a variable
say_hi = greet
# Pass it as an argument
def make_loud(func, name):
return func(name).upper()
print(say_hi("Sara")) # Hello, Sara!
print(make_loud(greet, "Sara")) # HELLO, SARA!
That’s the essence: a function can go anywhere a variable can.
Why docstrings and annotations matter
Python doesn’t force types on you, but leaving hints is a lifesaver for both humans and machines.
from typing import Callable, List
def transform(values: List[int], fn: Callable[[int], float]) -> List[float]:
"""Apply a function to each value and return a new list."""
return [fn(v) for v in values]
Docstrings = purpose and usage.
Annotations = expectations and contracts.
They make code easier to read, maintain, and integrate with editors or tools.
Lambda: pocket-sized functions
Instead of writing a full function with def
, you can spin up a one-liner:
square = lambda x: x * x
print(square(6)) # 36
They shine when combined with tools like sorted
:
products = [
{"name": "Keyboard", "price": 59},
{"name": "Mouse", "price": 20},
{"name": "Monitor", "price": 199},
]
sorted_products = sorted(products, key=lambda p: p["price"])
Quick, clean, and right to the point.
The quirky trick: shuffle with sorted
Ever seen someone randomize a list using… sorted
?
import random
data = [1, 2, 3, 4, 5]
shuffled = sorted(data, key=lambda _: random.random())
It works, but is more of a fun hack than a best practice. For serious work, random.shuffle
is better. Still, it’s a neat demonstration of how far key functions can go.
Introspection: looking inside functions
Python lets you peek into a function’s blueprint:
import inspect
def price_with_tax(price: float, rate: float = 0.1) -> float:
"""Calculate total price including tax."""
return price * (1 + rate)
print(price_with_tax.__name__) # price_with_tax
print(price_with_tax.__annotations__) # {'price': float, 'rate': float, 'return': float}
print(inspect.signature(price_with_tax)) # (price: float, rate: float=0.1) -> float
This ability powers tools like Django, FastAPI, and CLIs that auto-generate help text.
Callables aren’t just functions
Any object with a __call__
method can behave like a function:
class Counter:
def __init__(self):
self.count = 0
def __call__(self):
self.count += 1
return self.count
c = Counter()
print(c()) # 1
print(c()) # 2
This blends data with behavior, which can be incredibly elegant.
Map, filter, zip, and comprehensions
These are the assembly-line operators of Python:
names = ["anik", "sara", "lee"]
# Capitalize all names
proper = list(map(str.title, names))
# ['Anik', 'Sara', 'Lee']
# Keep only passing scores
scores = [95, 45, 82]
passed = list(filter(lambda s: s >= 60, scores))
# [95, 82]
# Pair students with scores
students = ["Anik", "Sara", "Lee"]
grades = [95, 88, 77]
paired = list(zip(students, grades))
# [('Anik', 95), ('Sara', 88), ('Lee', 77)]
List comprehensions often make this even clearer:
proper = [n.title() for n in names]
Reduce: folding into one value
reduce
takes a sequence and boils it down:
from functools import reduce
from operator import mul
nums = [2, 3, 4]
product = reduce(mul, nums, 1) # 24
Neat trick, but when possible, built-ins like sum
, any
, max
are cleaner.
Partial functions: pre-filled defaults
Use functools.partial
to create functions with some arguments locked in:
from functools import partial
def add_tax(price, rate):
return price * (1 + rate)
bd_tax = partial(add_tax, rate=0.15)
print(bd_tax(100)) # 115.0
This shines in config-heavy code (APIs, formatting, logging).
The operator
module: functional shortcuts
Why write tiny lambdas when Python already has helpers?
from operator import itemgetter
cities = [
{"name": "Dhaka", "pop": 21_000_000},
{"name": "Chattogram", "pop": 2_600_000},
]
largest = max(cities, key=itemgetter("pop"))
Readable, fast, and built for exactly these cases.
Wrapping it all up
First-class functions aren’t just an academic concept. They’re the engine of flexibility in Python:
Compose small behaviors into big workflows.
Keep your code DRY, elegant, and maintainable.
Use the standard library (
functools
,operator
,itertools
) like superpowers.
The beauty of Python here is that it doesn’t force you, but once you start writing functions as data, your code unlocks a whole new level.
Subscribe to my newsletter
Read articles from Anik Sikder directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Anik Sikder
Anik Sikder
Full-Stack Developer & Tech Writer specializing in Python (Django, FastAPI, Flask) and JavaScript (React, Next.js, Node.js). I build fast, scalable web apps and share practical insights on backend architecture, frontend performance, APIs, and Web3 integration. Available for freelance and remote roles.