First Things First — What Even Is a Function?
Before we talk about callbacks, we need to really get functions. Not just "oh yeah, I've used function before" — but actually understand what a function is.
Here's the simplest way to think about it:
A function is a named set of instructions that you can run whenever you want.
What that means is — instead of writing the same instructions over and over, you write them once, give them a name, and just call that name when you need them to run. Now how that sticks: think of it like a recipe card. The recipe card isn't the food. It's just the instructions. You can follow that recipe once, twice, a hundred times — but the card itself just sits there until you decide to use it.
function greet() {
console.log('Hey there, welcome!');
}This defines the recipe. Nothing happens yet.
greet(); // Now we're cookingThis runs it. That's the difference between defining a function and calling a function. Defining = writing the recipe. Calling = actually making the dish.
Functions Can Take Ingredients (Parameters)
A recipe that only ever makes one thing isn't very useful. What if we want to greet different people?
What parameters mean is — they're the inputs you hand to a function so it can do something specific instead of something fixed. Now how that sticks: think of a coffee machine. It knows how to make coffee — that's its function. But you tell it: one shot or two? With milk? That's the parameter. You're customising the output.
function greet(name) {
console.log('Hey there, ' + name + '!');
}
greet('Amara'); // Hey there, Amara!
greet('Ruguru'); // Hey there, Ruguru!
greet('Manny'); // Hey there, Manny!One function. Three different results. That's the power of parameters.
Functions Can Give You Something Back (Return Values)
Functions can also hand something back to you after they're done.
What return means is — it's the function saying "here, I finished, and here's what I came up with." Now how that sticks: you send a function on an errand. return is it coming back with the thing you asked for.
function addTax(price) {
return price * 1.16; // 16% VAT
}
let totalPrice = addTax(1000);
console.log(totalPrice); // 1160Without return, the function does its thing but keeps the result to itself. With return, it hands you back something you can actually use.
Here's the Plot Twist — Functions Are Just Values
This is the part that changes everything, so read it slowly.
In JavaScript, functions are values — just like numbers, strings, or arrays. What that means is — you can store a function in a variable, pass it to another function, or return it from a function, the same way you'd do with any other piece of data.
Now how that sticks: imagine you could fold up a recipe card and hand it to your friend and say "use this whenever you're ready." That's exactly what JavaScript lets you do with functions.
// Storing a function in a variable
const sayHi = function () {
console.log('Hi!');
};
sayHi(); // Hi!That function has no name — it's called an anonymous function. But we stored it in a variable called sayHi, so we can still call it. Same result, different approach.
You can even use the modern arrow function shorthand, which means the exact same thing, just written differently:
const sayHi = () => {
console.log('Hi!');
};Now We're Ready — What Is a Callback?
A callback is a function that you pass into another function, so that the other function can call it at the right moment.
What a callback means is — you're not just passing data to a function. You're passing instructions. You're saying "here's what I want you to do, but you decide when to do it."
Now how that sticks: think about ordering food at a restaurant. You don't stand in the kitchen watching your food get made. You give the waiter your order (the callback) and go sit down. When the food is ready, they call you. You handed off the instructions; they handled the timing.
function doSomethingAndThenCall(callback) {
console.log('Doing the thing...');
callback(); // Now we call the function that was passed in
}
function announce() {
console.log('The thing is done!');
}
doSomethingAndThenCall(announce);Output:
Doing the thing...
The thing is done!Notice — we passed announce without the parentheses. What that means is — we're passing the recipe card, not asking them to cook right now. If we wrote announce(), it would run immediately and pass the result instead. Big difference.
A Real Example — Let's Build a Custom .map()
You've probably heard of .map() — the built-in JavaScript array method that transforms every item in an array. Before we use the built-in, let's build our own so you can see exactly what's happening under the hood. This is where callbacks really click.
The Goal
Take an array of numbers, and double every single one.
const numbers = [1, 2, 3, 4, 5];
// We want: [2, 4, 6, 8, 10]Building Our Custom Map
function customMap(array, callback) {
const result = []; // Start with an empty bucket
for (let i = 0; i < array.length; i++) {
const transformed = callback(array[i]); // Run the callback on each item
result.push(transformed); // Drop the result into our bucket
}
return result; // Hand back the filled bucket
}Let's break down what's happening here:
array— the list of items we want to transformcallback— the instructions for how to transform each item (this is our callback!)- We loop through every item, run it through the callback, and collect the results
Now let's use it:
function double(number) {
return number * 2;
}
const doubled = customMap(numbers, double);
console.log(doubled); // [2, 4, 6, 8, 10]customMap doesn't know what "transform" means. You tell it, by passing double as the callback. Now how that sticks: customMap is the machine on the factory floor. double is the instruction sheet you hand it for today's job. Tomorrow you can hand it a different instruction sheet, and it'll do something completely different — same machine, new job.
Want to Triple Instead? Just Swap the Callback
function triple(number) {
return number * 3;
}
const tripled = customMap(numbers, triple);
console.log(tripled); // [3, 6, 9, 12, 15]Same customMap. Different callback. Different result. That's the beauty of it.
Even Cleaner — With an Arrow Function
Most of the time, when the transformation is simple, you won't even bother naming the function. You'll just write it inline:
const doubled = customMap(numbers, (number) => number * 2);
console.log(doubled); // [2, 4, 6, 8, 10]What that arrow function means is — we're defining and passing the callback in one go, right there on the spot. It's the same as the double function above, just written inline because it's short enough that naming it separately would be overkill.
Now Look at the Built-In .map()
JavaScript's built-in .map() works exactly the same way as our customMap — we just didn't have to write the loop ourselves:
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled); // [2, 4, 6, 8, 10]The .map() does the looping. You provide the callback — the instructions for what to do with each item. That's it.
The Big Takeaway
Let's recap the journey:
- Functions are named sets of instructions you can run whenever you need them
- Parameters let functions work with different inputs
- Return values let functions hand results back to you
- Functions are values — you can store them and pass them around like any other data
- Callbacks are functions you pass into other functions, so they can be called at the right moment
.map()is a perfect example — you hand it a callback that says how to transform each item, and it handles the rest
The moment callbacks click is the moment JavaScript starts feeling less like magic and more like a language you actually speak. You're not just writing code that runs top to bottom — you're handing off instructions, composing behaviours, and telling your code what to do and when to do it.
And that? That changes everything.
