Not Every `function` Keyword Writes a Function

Why using the function keyword doesn't make something a function, and the one missing piece that turns a procedure into the real thing.

September 2, 20263 min read3 / 10

The last post made the case for writing declarative code that states an outcome instead of narrating steps. Functional programming is how you actually get there, and it all rests on one word: function. I used to think a function was anything written with the function keyword. That belief lasted until I actually tried to write functional code, and realized most of what I called "functions" didn't qualify.

The function keyword is just syntax. It says nothing about what the thing actually does.

A Function That Isn't One

Say you're logging an order summary in an e-commerce checkout:

JavaScript
function logOrderTotal(price, tax, shipping) { const total = price + tax + shipping; console.log(`Order total: $${total}`); }

It takes inputs. It does something with them. It even feels useful. But it never hands anything back to whoever called it.

Call logOrderTotal(40, 4, 5) and try to use the result:

JavaScript
const result = logOrderTotal(40, 4, 5); console.log(result); // undefined

undefined. Every JavaScript function returns something even when you don't write return, but that default return is nothing at all. That's the gap.

The Missing Requirement

A function has to take input and hand back an output. Not print it. Not log it. Return it.

If a block of code has no return keyword, it isn't a function. It's a procedure.

A procedure is any named block of operations. Procedures aren't bad. Every program needs them: things that print to a screen, write to a database, or trigger a side effect. logOrderTotal is a perfectly fine procedure.

It just doesn't get to call itself a function.

Procedure
JavaScript
function logOrderTotal(price, tax) { console.log(price + tax); }

Does something with its inputs, but hands nothing back.

Function
JavaScript
function getOrderTotal(price, tax) { return price + tax; }

Takes inputs, computes a value, and returns it.

It's Contagious

Here's the part that surprised me. Adding a return keyword doesn't automatically make something a function either.

JavaScript
function applyDiscount(price, tax) { return logOrderTotal(price, tax); }

applyDiscount has a return statement. It looks like a function. But it's returning whatever logOrderTotal gives back, and logOrderTotal always gives back undefined.

A function can only call other functions. Call a procedure, and you become one too.

This isn't a special rule just for this example, it's structural. Whatever a function calls internally decides what it can honestly return, all the way down the call chain. One procedure buried three calls deep taints everything sitting on top of it.

A Venn diagram showing functions as a smaller circle fully contained inside the larger circle of procedures ExpandA Venn diagram showing functions as a smaller circle fully contained inside the larger circle of procedures

Every function is a procedure, since it's still a named block of operations. Not every procedure is a function. That's the actual shape of the relationship, and it's why the function keyword alone never settles the question.

More Than One Output

There's a second wrinkle worth catching early: a function's "one output" can still carry more than one value, as long as it's returned honestly.

JavaScript
function priceRange(base) { return [base - 5, base + 5]; } const [min, max] = priceRange(20); // min = 15, max = 25

priceRange technically returns one array. But the moment you destructure it into min and max, you're telling the reader something different: this function actually has two outputs, packaged as one value so it can travel through a single return.

For now, the rule stays simple: no return, no function, no exceptions.

What This Buys You

Later in this series, you'll chain small functions together like pipes: one function's output feeds directly into the next function's input.

JavaScript
const total = addTax(applyDiscount(getPrice(item)));

That chain only works if every piece in it actually behaves like a function. Feed applyDiscount a procedure by mistake, and the whole chain silently carries undefined forward, with no error to warn you.

That's the entire payoff of this post. Knowing whether something is a function or a procedure is what tells you whether it's safe to plug into a chain like that one at all.

Having a return is the first requirement, not the last. The next thing to nail down is what that return value is allowed to depend on, and that's where function purity actually starts.