JavaScript event listeners are very important because they exist in almost every web application that requires interactivity. As common as they are, it is also essential to manage them properly. Poorly managed event listeners can cause memory leaks and, in some extreme cases, performance issues.
Here is the real problem: JavaScript event listeners are often not removed after they are added. And when they are added, they usually do not need parameters — except in rare cases that make them a little more complicated to manage.
A common scenario where you might need to use parameters with event handlers is a dynamic task list, where each task has a “Delete” button connected to an event handler that uses the task ID as a parameter to remove the task. In this situation, once the task is completed, it is good practice to remove the event listener to ensure that the deleted element can be successfully cleaned up — a process known as garbage collection.
A common mistake when adding event listeners
A very common mistake when adding parameters to event handlers is calling the function with its parameters inside the addEventListener() method. Here is what we mean:
button.addEventListener('click', myFunction(param1, param2));
The browser responds to this line by immediately calling the function, regardless of whether the click event has happened or not. In other words, the function is called right away instead of being deferred, so it never runs when the click event actually occurs.
In some cases, you may also get this error in the console:

This error makes sense because the second parameter of the addEventListener method can only accept a JavaScript function, an object with a handleEvent() method, or simply null. A quick and simple way to avoid this error is to change the second parameter of the addEventListener method into an arrow or anonymous function.
button.addEventListener('click', (event) => {
myFunction(event, param1, param2); // Runs on click
});
The only drawback of using arrow and anonymous functions is that they cannot be removed with the traditional removeEventListener() method; you will need to use AbortController, which may be excessive for simpler cases. AbortController is excellent when you need to remove multiple event listeners at once.
For simpler cases where you only need to remove one or two event listeners, the removeEventListener() method is still useful. However, to use it, you need to save your function as a reference to the listener.
Using parameters with event handlers
There are several ways to include parameters in event handlers. For the purpose of this demonstration, however, we will limit ourselves to these two:
Option 1: Arrow and anonymous functions
Using arrow and anonymous functions is the fastest and easiest way to get the job done.
To add an event handler with parameters using arrow and anonymous functions, we first need to call the function we are going to create inside the arrow function attached to the event listener:
const button = document.querySelector("#myButton");
button.addEventListener("click", (event) => {
handleClick(event, "hello", "world");
});
After that, we can create the function with parameters:
function handleClick(event, param1, param2) {
console.log(param1, param2, event.type, event.target);
}
Note that when using this method, you need AbortController to remove the event listener. To remove the listener, we create a new AbortController object and get the AbortSignal object from it:
const controller = new AbortController();
const { signal } = controller;
Then we can pass the signal from the controller as an option in the addEventListener() method:
button.addEventListener("click", (event) => {
handleClick(event, "hello", "world");
}, { signal });
Now we can remove the event listener by calling AbortController.abort():
controller.abort();
Option 2: Closures
Closures in JavaScript are another feature that can help us with event handlers. Remember the mistake that caused the type error? That error can also be fixed with closures. Specifically, closures allow a function to access variables from its outer scope.
In other words, we can access the parameters needed by the event handler from an outer function:
function createHandler(message, number) {
// Event handler
return function (event) {
console.log(`${message} ${number} - Clicked element:`, event.target);
};
}
const button = document.querySelector("#myButton");
button.addEventListener("click", createHandler("Hello, world!", 1));
}
This creates a function that returns another function. The created function is then called as the second parameter of the addEventListener() method, so the inner function is returned as the event handler. And with the power of closures, the parameters from the outer function will be available for use inside the inner function.
Notice how the event object becomes available to the inner function. This is because the inner function is the one attached as the event handler. The event object is passed to the function automatically because it is an event handler.
To remove the event listener, we can use AbortController, as we did earlier. This time, however, let’s look at how we can do it using the removeEventListener() method.
For the removeEventListener method to work, the reference to the createHandler function must be saved and used in the addEventListener method:
function createHandler(message, number) {
return function (event) {
console.log(`${message} ${number} - Clicked element:`, event.target);
};
}
const handler = createHandler("Hello, world!", 1);
button.addEventListener("click", handler);
Now the event listener can be removed like this:
button.removeEventListener("click", handler);
Conclusion
It is good practice to always remove event listeners when they are no longer needed to avoid memory leaks. Most of the time, event handlers do not need parameters; however, in rare cases, they do. Using JavaScript features such as closures, AbortController, and removeEventListener, managing parameters with event handlers is possible and well supported.



