10 — Functions — First-Class Values, and the Arrow Distinction
"Blocks of reusable code" described what functions do without explaining their power. The idea that everything else hangs off: functions in JavaScript are first-class values — they can be assigned, passed as arguments, and returned from other functions — and that single property is why callbacks, closures, and functional patterns work at all. [1]
The framing that finally landed is treating a function as a value, not a special thing. A function declaration creates a value of type "function" and binds a name to it; that value can then flow anywhere a number or string can. Once I saw it that way, the rest followed: passing onClick as an argument, returning a function from a factory, storing functions in an array and calling them in a loop — all just value-flow.
Three ways to write one
There are three syntaxes, each subtly different [1][2]:
// 1. Function declaration — hoisted with body, callable before definition
function add(a, b) { return a + b; }
// 2. Function expression — not hoisted; assigned to a name
const add = function(a, b) { return a + b; };
// 3. Arrow function — concise, no own this/arguments
const add = (a, b) => a + b;The practical difference between declaration and expression is hoisting: declarations are hoisted with their body, so they can be called before their line; expressions are not. In modern code I lean on declarations for top-level named functions (the hoisting makes call-ordering flexible) and arrow functions for everything inline.
Arrow functions and what they don't bind
Arrow functions are the ES6 addition that changed how I write JS. The => syntax is concise, but the real distinction is semantic: arrows don't have their own this or arguments binding [3]. They inherit both from the surrounding lexical scope. That has two consequences:
- Inside an arrow, this is whatever this was one scope up — predictable, not rebound per call. This makes arrows ideal for callbacks where a regular function would lose the outer this.
- Arrows have no arguments object. If I need variadic arguments, I use rest parameters instead.
The flip side: arrows are the wrong choice for object methods that rely on this, or for constructor-like functions. For methods, a regular function or shorthand method is correct, because I want this bound to the receiver.
Parameters: default and rest
Two parameter features cover most real needs [4][5]:
- Default parameters — function greet(name = "friend"). If name is undefined (or omitted), it falls back to "friend". Cleaner than name || "friend" because it doesn't replace other falsy values.
- Rest parameters — function sum(...nums). Collects any extra arguments into a real array. The modern replacement for the arguments object, which is array-like but lacks .map/.filter.
function greet(name = "friend", ...hobbies) {
return `Hi ${name}, you like ${hobbies.join(", ")}`;
}
greet("Ave", "code", "music"); // "Hi Ave, you like code, music"IIFEs — and why they mostly faded
An IIFE (Immediately Invoked Function Expression) is a function defined and called in one move [6]:
(function() {
const private = "hidden"; // scoped here, not leaked globally
console.log("ran once");
})();Before ES6 modules, IIFEs were the pattern for creating private scope and avoiding global pollution. With let/const block scope and ES modules, they're mostly historical — but I still reach for one occasionally to scope a temporary variable in a script, or to run an async top-level expression ((async () => { await … })()).
Scope, the call stack, and closures
Two mechanics underpin every function call [7]:
- The call stack — each call pushes a frame (the function's local variables and return address); each return pops it. Stack overflow is what happens when recursion never pops.
- Scope — where a name is visible. Functions create their own scope; the chain of nested scopes is what a name lookup walks.
Closures are the payoff of these two. A closure is a function bundled with the variables from the scope where it was defined — even after that scope has exited [8]:
function counter() {
let count = 0;
return () => ++count; // this returned function closes over `count`
}
const next = counter();
next(); // 1
next(); // 2 — `count` is still alive, held by the closureThe local variable count would normally be garbage-collected when counter returns. But the returned function closes over it, keeping it alive as long as the function exists. Closures are how privacy, factories, partial application, and most functional patterns work in JavaScript. They aren't an advanced feature — they're the default behavior of every function.
Built-in functions
Beyond what I write, JavaScript ships a library of built-in functions available globally or on standard objects: parseInt, parseFloat, setTimeout, setInterval, isNaN, the Math methods (Math.max, Math.random), and the methods on Array, String, Object, Date [9]. Knowing the standard library exists — and reaching for Array.from or Object.entries instead of hand-rolling — is what keeps code short. I periodically skim the Math and Array method lists; there's almost always a built-in for whatever I was about to write.
How I use this
The choices collapsed to two rules. For named, reusable functions I write a function declaration. For anything inline — callbacks, short transformations, JSX handlers — I write an arrow function, trusting it to inherit this rather than rebind it. Default parameters replace || fallbacks; rest parameters replace arguments. Closures I stopped thinking of as a technique and started treating as the default — any function that returns a function is using one, and that's fine. The idea "functions are values" is the one that rewired how I read code: a callback isn't a special pattern, it's just a value being passed.
References
[1] Mozilla, "Functions," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions
[2] I. Kantor, "Functions," The Modern JavaScript Tutorial, 2024. [Online]. Available: https://javascript.info/function-basics
[3] Mozilla, "Arrow function expressions," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
[4] Mozilla, "Default parameters," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters
[5] Mozilla, "Rest parameters," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters
[6] Mozilla, "IIFE (Glossary)," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Glossary/IIFE
[7] Mozilla, "Call stack (Glossary)," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Glossary/Call_stack
[8] I. Kantor, "Closure," The Modern JavaScript Tutorial, 2024. [Online]. Available: https://javascript.info/closure
[9] TutorialsPoint, "JavaScript built-in functions," 2023. [Online]. Available: https://www.tutorialspoint.com/javascript/javascript_builtin_functions.htm
Knowledge check · Question 1 of 5
What makes a function "first-class" in JavaScript?
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!