Skip to content

Event Listeners — Complete Guide

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Event Listeners. We cover key concepts, practical examples, and best practices to help you master this topic.

Event listeners enable JavaScript to respond to user interactions like clicks, key presses, and form submissions through the addEventListener method on DOM elements.

What You'll Learn

  • How to attach and remove event listeners with addEventListener and removeEventListener
  • The event object properties: target, currentTarget, type, preventDefault, stopPropagation
  • The difference between event handler properties and addEventListener
  • Event listener options: once, passive, capture

Why It Matters

Everything interactive on the web relies on events. Without event listeners, buttons do nothing, forms cannot be submitted, and pages are completely static. Mastering events is essential for building any interactive application.

Real-World Use

  • A "Like" button sends an AJAX request when clicked
  • A search input triggers live suggestions after the user stops typing
  • A game responds to keyboard arrow keys for character movement
flowchart LR
  A[User Action] --> B[Event Fired]
  B --> C[Event Object Created]
  C --> D[Capture Phase]
  D --> E[Target Phase]
  E --> F[Bubble Phase]
  F --> G[Event Listener Fires]
  G --> H[Callback Function]
  H --> I[Respond to Event]

Adding Event Listeners

The addEventListener method attaches a function to be called when a specific event type occurs on the element.

// Basic click listener
const button = document.querySelector('.click-me');
button.addEventListener('click', function(event) {
    console.log('Button was clicked!');
    console.log('Event type:', event.type);
    console.log('Target element:', event.target);
});

// Arrow function syntax
button.addEventListener('click', (e) => {
    console.log('Clicked via arrow function');
});

// Named function (useful for removal later)
function handleClick(event) {
    console.log('Named handler executed');
}
button.addEventListener('click', handleClick);
// button.removeEventListener('click', handleClick);

Expected output: When the button is clicked, the console shows three messages from the three handlers. Each handler receives the event object with type and target properties.

The Event Object

Every event listener receives an event object with useful properties and methods.

const link = document.querySelector('a');

link.addEventListener('click', function(event) {
    // Prevent default behavior (navigation)
    event.preventDefault();
    console.log('Default prevented');

    // Event properties
    console.log('Event type:', event.type);
    console.log('Target:', event.target);
    console.log('Current target:', event.currentTarget);
    console.log('Timestamp:', event.timeStamp);
    console.log('Bubbles:', event.bubbles);
    console.log('Cancelable:', event.cancelable);

    // Coordinates for mouse events
    if (event.clientX !== undefined) {
        console.log('Mouse X:', event.clientX);
        console.log('Mouse Y:', event.clientY);
    }
});

Expected output: Clicking the link logs the event details. The default navigation is prevented — the browser does not follow the link.

Listener Options

The third argument to addEventListener can be an options object or a boolean (for capture phase).

const btn = document.querySelector('.once-btn');

// Option 1: Fire only once
btn.addEventListener('click', function() {
    console.log('This runs only once');
}, { once: true });

// Option 2: Passive listener (performance hint for scroll)
window.addEventListener('touchstart', function(event) {
    // Do not call preventDefault() inside passive listeners
    console.log('Touch started');
}, { passive: true });

// Option 3: Capture phase
document.body.addEventListener('click', function(event) {
    console.log('Body capture phase');
}, { capture: true });

// Equivalent shorthand for capture
// document.body.addEventListener('click', handler, true);

// Option 4: Abort signal (for cleanup)
const controller = new AbortController();
window.addEventListener('resize', function() {
    console.log('Window resized');
}, { signal: controller.signal });
// Later: controller.abort(); removes the listener

Expected output: The once button fires only on the first click. The touch listener does not block scrolling. The body capture listener fires before the target phase.

Event Handler Properties

Before addEventListener, events were assigned via properties like onclick and onmouseover.

const button = document.querySelector('.old-style');

// Property assignment (only one handler at a time)
button.onclick = function(event) {
    console.log('First handler');
};

// This overwrites the first handler
button.onclick = function(event) {
    console.log('Second handler');
};

// Only "Second handler" will run

// addEventListener allows multiple handlers
button.addEventListener('click', function() {
    console.log('Third handler');
});
button.addEventListener('click', function() {
    console.log('Fourth handler');
});

// Both "Third handler" and "Fourth handler" run alongside the onclick

Expected output: Clicking the button runs only "Second handler" from the onclick property, plus both addEventListener handlers. The first onclick handler is overwritten.

Removing Event Listeners

To remove a listener, you must pass the exact same function reference to removeEventListener.

const button = document.querySelector('.toggle-btn');

function handleToggle(event) {
    console.log('Toggle handler executed');
    // Remove this listener after first use
    button.removeEventListener('click', handleToggle);
    console.log('Listener removed');
}

button.addEventListener('click', handleToggle);

// This WILL NOT work (different function reference)
button.removeEventListener('click', function() {
    console.log('This does nothing');
});

// Anonymous functions cannot be removed
// Always use named functions if you need to remove them later

Expected output: The first click runs the handler and removes it. Subsequent clicks do nothing because the listener has been removed. The anonymous function passed to removeEventListener does not match any listener.

Dispatching Custom Events

You can create and dispatch your own events.

const element = document.querySelector('.custom-event-target');

// Create a custom event
const customEvent = new CustomEvent('userAction', {
    detail: {
        userId: 42,
        action: 'login',
        timestamp: Date.now()
    },
    bubbles: true,
    cancelable: true
});

// Listen for the custom event
element.addEventListener('userAction', function(event) {
    console.log('Custom event received!');
    console.log('Detail:', event.detail);
    console.log('User ID:', event.detail.userId);
    console.log('Action:', event.detail.action);
});

// Dispatch the event
element.dispatchEvent(customEvent);

// Custom events can be listened on any element and bubble up
document.addEventListener('userAction', function(event) {
    console.log('Caught by document (bubbling)');
});

Expected output: When the custom event is dispatched, both the element listener and the document listener fire. The detail object passes the custom data to all listeners.

Common Mistakes

  1. Adding the same listener twice — If you add the exact same function reference with addEventListener, it executes twice for each event. The listener is not deduplicated.
  2. Using anonymous functions with removeEventListener — Anonymous functions create a new reference each time. You cannot remove them. Always use named functions if removal is needed.
  3. Forgetting to prevent default on form submissions — Form submission events cause a page reload by default. Call event.preventDefault() in the submit handler.
  4. Adding listeners inside loops without closure handling — Loop variables captured by reference cause all listeners to see the last value. Use let or an IIFE to capture the correct value.
  5. Not cleaning up listeners on element removal — Removed elements with attached listeners can cause memory leaks if the listener closure references external variables.

Practice Questions

  1. What is the difference between addEventListener and onclick? addEventListener supports multiple handlers, options (once, passive), and capture phase. onclick only supports one handler and overwrites previous ones.
  2. How do you remove an event listener? Call removeEventListener with the same event type and function reference. The function must be a named function, not an anonymous one.
  3. What does the once option do? It automatically removes the listener after the first invocation. Equivalent to calling removeEventListener inside the handler.
  4. Challenge: Create a debounced event listener. When the user types in an input field, the callback should fire only after they stop typing for 300ms. Cancel the previous timeout on each keystroke.

FAQ

Can I add multiple listeners of the same type to the same element?

Yes. addEventListener does not replace existing listeners. Both execute in the order they were added.

What is the default value for the capture option?

false. The listener fires in the bubbling phase, not the capture phase.

Do event listeners work on SVG elements?

Yes. SVG elements support addEventListener for most standard DOM events.

What happens if I call preventDefault on a non-cancelable event?

Nothing. The method does nothing for events where cancelable is false. Check event.cancelable before calling preventDefault.

Is there a limit to how many event listeners I can add?

No hard limit, but each listener consumes memory. Add only the listeners you need. Remove them when they are no longer needed.

Mini Project

Build a custom dropdown menu. The dropdown toggle button shows/hides a menu list on click. Clicking outside the dropdown closes it. Use event listeners on the document to detect outside clicks. Use stopPropagation to prevent the dropdown click from triggering the document listener. Add keyboard support: Escape closes the dropdown, Arrow keys navigate items.

What's Next

Continue with Lesson 13: Event Bubbling and Capturing to understand how events propagate through the DOM tree in capture and bubble phases.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro