What is a callback — and why extra parentheses break your button

Here's a fun one: you've already written callbacks, even if you never knew the word. Every button you've built with AI is a callback.
And it comes with a trap exactly two characters wide. onClick={save} works. onClick={save()} breaks your app — sometimes fatally, with an endless re-render. Let's see why one pair of symbols decides so much.
What a callback is
A callback is a function you hand to someone else so they can call it themselves, when the time comes.
Usually functions work the other way: you decide when to run them. save() — and it runs right now. But there's a pile of situations where the moment isn't yours to pick:
- The user will press a button. When? Unknown.
- The server will answer a request. In 200 milliseconds, or in three seconds.
- A timer will fire. A file will finish reading. A message will land.
In all of those you can't write "call save right here" — right here is too early. So you hand over the function itself: "here's the instruction, run it when the event happens."
Hence the name: call back — "ring me when you're ready." You leave a number instead of holding the line.
Where you've already seen one
Three places that show up in any generated code:
button.addEventListener("click", save)
setTimeout(showHint, 3000)
items.filter(isCheap)
In every line the function is passed by name, without parentheses. save, showHint, isCheap — those aren't calls, those are handoffs.
That last one is especially useful. filter walks the whole array itself, but which items count as matches is your call. The walking machinery is theirs, the rule is yours — and it's handed over as a callback.
That one pair of parentheses
Now the breakage. The difference between the two forms is fundamental:
saveis the function itself. A thing you can store in a variable and pass along.save()is the result of running it. The function already ran, and what you're holding is what it returned.
So in React code onClick={save} means "here's a function, call it on click". And onClick={save()} means "run save right now, during render, and attach whatever it returned to the click" — usually undefined.
The consequences can be loud. If save changes component state, calling it during render triggers another render, which triggers another call, and the app spins forever. So when a screen freezes solid after a harmless edit, go looking for extra parentheses in the handlers first.
What if you need to pass an argument? Then you wrap it in an arrow function: onClick={() => save(item.id)}. It reads as "on click, run this" — and that's exactly how AI writes 90% of handlers. Now you know it isn't magic syntax, just a callback created on the spot.
Where the argument you never passed comes from
The question that stalls almost everyone. You look at the code and can't see where item came from:
const cheap = items.filter(item => item.price < 100)
You never wrote what item equals. It's supplied by whoever calls your callback. filter walks the list and on each round calls your function, handing it the next item. All you did was pick the variable's name — call it x and it works exactly the same.
Same with events: button.addEventListener("click", (event) => ...). The event argument comes from the browser, and inside it is what was clicked, where and when. Simple rule: the caller decides a callback's arguments, you receive them. Which gives you a practical move — when you can't remember what you're being handed, print it: console.log(arguments) as the first line shows everything that arrived.
Callbacks and async
Historically, callbacks were the main way to handle async work: "go to the server, and when you're back, call this function."
The problem showed up once there were several steps. Load the user, then their orders, then the products in an order — each callback nested inside the previous one, and the code staircased off to the right. It even has a name: callback hell.
That's why promises and async/await arrived — same meaning, but the code reads top to bottom. In new code AI almost always writes await instead of nested callbacks. Callbacks didn't go anywhere though: event handlers, map, filter, setTimeout are all still callbacks. Just flat ones, without the nesting.
Is a callback the same as an arrow function?
No, those are different things. An arrow function is a way of writing. A callback is a role: a function handed over so somebody else can call it. They're written as arrow functions simply because it's short.
Why didn't my callback fire?
Three usual causes. You called the function instead of passing it — extra parentheses. A typo in the name: onCLick instead of onClick — JavaScript stays silent, no error. Or the event never arrived: the element is covered by another one and the click went to the neighbour. Quick check — drop a console.log as the function's first line and read the logs: silence means it was never called, not that something broke inside.
Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.





