A function is a named piece of work you can run more than once. Functional programming is a set of habits about how you write them: return values instead of changing things, avoid hidden state, and build bigger operations out of small ones. You do not need to adopt a functional language to get most of the benefit — the ideas apply directly to Python, JavaScript, Java and C#.
The basics, briefly#
def area(width, height):
return width * height
print(area(3, 4)) # 12
print(area(height=4, width=3)) # same, named arguments
Three parts: the name, the parameters it accepts, and what it gives back. A function that gives nothing back returns None in Python, undefined in JavaScript, and is declared void in Java and C#.
Variables created inside a function are local to it:
def calculate():
total = 10 # only exists inside this function
return total
calculate()
print(total) # NameError - total is not defined out here
That isolation is the point. It means you can read a function and understand it without reading the rest of the program.
Pure functions#
A function is pure when two things are true: the same inputs always give the same output, and it changes nothing outside itself.
# Pure
def add_tax(price, rate):
return price * (1 + rate)
# Not pure - reads something outside itself
TAX_RATE = 0.2
def add_tax(price):
return price * (1 + TAX_RATE)
# Not pure - changes something outside itself
totals = []
def record(price):
totals.append(price)
Pure functions are easier to work with for concrete reasons, not aesthetic ones:
- You can test them in one line. No setup, no database, no mocking — call it and check what comes back.
- You can read them in isolation. Nothing elsewhere in the program can change what they do.
- You can cache them. If the inputs match a previous call, the previous answer is still correct.
- You can run them in parallel without worrying about two of them clashing.
Not everything can be pure — something has to write files, print output and talk to databases. The useful goal is to push those effects to the edges and keep the decision-making in the middle pure.
map, filter and reduce#
These three cover most of what loops do to collections.
numbers = [1, 2, 3, 4, 5, 6]
# map - transform every item
doubled = [n * 2 for n in numbers]
# filter - keep some items
evens = [n for n in numbers if n % 2 == 0]
# reduce - collapse to a single value
total = sum(numbers)
Python prefers comprehensions to the map and filter functions, but the concepts are identical. In JavaScript the methods are used directly:
const numbers = [1, 2, 3, 4, 5, 6];
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const total = numbers.reduce((sum, n) => sum + n, 0);
The gain is that the name says what is happening. A for loop could be doing anything; filter can only be removing items.
They chain, which is where the style earns its keep:
const total = orders
.filter(o => o.status === "paid")
.map(o => o.amount)
.reduce((sum, amount) => sum + amount, 0);
Immutability#
Changing data in place causes bugs that are hard to trace, because the change is visible everywhere the data is referenced:
def add_bonus(scores):
scores.append(10) # modifies the caller's list
return scores
original = [1, 2, 3]
result = add_bonus(original)
print(original) # [1, 2, 3, 10] - the caller's list changed too
Return a new value instead:
def add_bonus(scores):
return scores + [10] # a new list
original = [1, 2, 3]
result = add_bonus(original)
print(original) # [1, 2, 3] - untouched
print(result) # [1, 2, 3, 10]
In JavaScript the spread operator does the same job:
const withBonus = [...scores, 10];
const updated = { ...user, name: "Ana" };
Functions as values#
In most modern languages a function is a value you can store, pass and return.
def shout(text):
return text.upper()
action = shout # no brackets - the function itself
print(action("hello")) # HELLO
names = ["ana", "sam"]
print([action(n) for n in names]) # ['ANA', 'SAM']
Passing a function as an argument is how sorting keys work:
people = [{"name": "Ana", "age": 34}, {"name": "Sam", "age": 28}]
by_age = sorted(people, key=lambda p: p["age"])
Returning a function lets you build one that remembers something:
def multiplier(factor):
def multiply(n):
return n * factor # remembers factor
return multiply
double = multiplier(2)
triple = multiplier(3)
print(double(10), triple(10)) # 20 30
That inner function keeping hold of factor after the outer one has finished is called a closure. It is the same mechanism behind event handlers and decorators.
Composition#
Small functions combine into bigger ones without either knowing about the other:
def strip_spaces(text):
return text.strip()
def lowercase(text):
return text.lower()
def clean(text):
return lowercase(strip_spaces(text))
print(clean(" Hello World ")) # hello world
Each piece is a few lines, testable on its own, and reusable somewhere else. That is the practical payoff of the whole style: not elegance, but that you can change one part without reading the others.
Questions people ask#
Do I have to learn a functional language?
No. Haskell and Elixir push the ideas further, and they are interesting, but the useful parts — pure functions, avoiding shared mutable state, map and filter — all work in the languages you already use.
Is functional code slower?
Creating new lists rather than modifying in place costs some memory and time. In almost all application code the difference is irrelevant next to the reduction in bugs. In a hot inner loop, measure before deciding.
What is a lambda?
A short function with no name, written inline. Useful as a sorting key or a one-line callback. If it needs a comment to explain, give it a real name with def instead.
Is object-oriented programming the opposite of this?
No, and treating them as rivals is a false choice. Most good codebases use objects for structure and functional habits inside methods. The two ideas answer different questions.
Where to go next#
- Python functions explained — the same ideas with Python specifics.
- What is object-oriented programming? — the other main way to organise code.
- JavaScript array methods reference — map, filter and reduce in full.