Functions as First-Class Citizens
In JavaScript, functions are first-class values. This means you can assign them to variables, pass them as arguments, and return them from other functions. This is the foundation of functional programming.
// Functions can be stored in variables
const sayHello = function() { return "Hello!"; };
// Passed as arguments (callbacks):
function executeAfterDelay(callback, delay) {
setTimeout(callback, delay);
}
executeAfterDelay(() => console.log("Executed!"), 1000);
// Returned from functions:
function createGreeter(greeting) {
return (name) => `${greeting}, ${name}!`;
}
const hello = createGreeter("Hello");
const goodbye = createGreeter("Goodbye");
console.log(hello("Alice")); // "Hello, Alice!"
console.log(goodbye("Bob")); // "Goodbye, Bob!"
// Stored in arrays and objects:
const operations = {
add: (a, b) => a + b,
sub: (a, b) => a - b,
mul: (a, b) => a * b,
div: (a, b) => b !== 0 ? a / b : null
};
console.log(operations.add(3, 4)); // 7
Higher-Order Functions
A higher-order function either takes a function as an argument or returns a function. They're the core of JavaScript's array methods and functional patterns.
// Built-in higher-order functions:
const numbers = [1, 2, 3, 4, 5];
// map — transform each element
const doubled = numbers.map(n => n * 2);
// filter — keep elements matching predicate
const evens = numbers.filter(n => n % 2 === 0);
// reduce — fold into single value
const sum = numbers.reduce((acc, n) => acc + n, 0);
// sort — with custom comparator
const words = ["banana", "apple", "cherry"];
words.sort((a, b) => a.localeCompare(b)); // alphabetical
const scores = [85, 92, 78, 96, 88];
scores.sort((a, b) => b - a); // descending: [96,92,88,85,78]
// Building your own higher-order function:
function applyTwice(fn, value) {
return fn(fn(value));
}
const double = n => n * 2;
console.log(applyTwice(double, 3)); // 12 (3*2=6, 6*2=12)
// Function composition
const compose = (...fns) => (x) => fns.reduceRight((acc, fn) => fn(acc), x);
const pipe = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc), x);
const addOne = n => n + 1;
const square = n => n * n;
const negate = n => -n;
const transform = pipe(addOne, square, negate);
console.log(transform(3)); // -((3+1)²) = -16
Callbacks in Async Code
// Before promises, callbacks were the only way to handle async:
function fetchData(url, onSuccess, onError) {
// Simulating async operation:
setTimeout(() => {
if (url.startsWith("https")) {
onSuccess({ data: "some data" });
} else {
onError(new Error("Must use HTTPS"));
}
}, 1000);
}
fetchData(
"https://api.example.com/users",
(result) => console.log("Success:", result),
(error) => console.error("Failed:", error)
);
// Callback Hell — the problem with nested callbacks:
fetchUser(userId, (user) => {
fetchPosts(user.id, (posts) => {
fetchComments(posts[0].id, (comments) => {
// 3 levels deep already... imagine 5 or 6
processComments(comments, (result) => {
console.log(result); // buried!
});
});
});
});
// This is why Promises were invented (next module!)
⚡ Key Takeaways
- Functions are first-class — store them, pass them, return them
- Higher-order functions take/return functions — core of functional JS
- Array methods (map, filter, reduce) are built-in higher-order functions
- Callbacks are functions passed as arguments to be called later
- Callback hell = deeply nested callbacks — solved by Promises and async/await
- Function composition combines small functions into more complex ones
🎯 Practice Exercises
EXERCISE 1
Implement map, filter, and reduce from scratch without using the built-in versions.
EXERCISE 2 — CHALLENGE
Build a simple event system: createEventEmitter() that returns an object with on(event, callback) and emit(event, data) methods.