By WebNest Studio Engineering Team
JavaScript Tutorial
Events and Event Handling
Events are how the browser tells your JavaScript that something happened — a click, a keypress, a page load, a form submission, a network response. Almost all interactivity on the web is built by listening for events and reacting to them.
Understanding not just how to attach a listener, but how events travel through the DOM (capturing and bubbling), unlocks powerful patterns like event delegation that make code both simpler and more efficient.
Why it matters
A web page does nothing until the user acts: clicks, types, scrolls, submits. Events are how code responds. Event delegation, where one listener on a parent handles all its children, is how lists of any length are handled efficiently, and how events travel through the page is a standard interview question.
addEventListener and the Event Object
element.addEventListener('click', handler) registers a function to run whenever that event fires on that element. Unlike the older onclick = handler style, addEventListener allows multiple listeners on the same event without overwriting each other, and it accepts an options object ({ once: true, capture: true, passive: true }). Every handler receives an event object describing what happened — event.target (the actual element that triggered it), event.type, event.preventDefault() (stops the default browser action, like following a link), and event.stopPropagation() (stops the event from continuing to travel).
Bubbling and Capturing
When an event fires on an element, it doesn't just affect that element — it travels through the DOM tree in two phases. The capturing phase goes from the document down to the target element; the bubbling phase then goes back up from the target to the document. By default, addEventListener listens during the bubbling phase; passing { capture: true } listens during capturing instead. This is why a click on a button inside a div also triggers click listeners on that div and on document, unless stopPropagation() is called.
Event Delegation
Instead of attaching a listener to every individual list item, button, or row (which is wasteful and doesn't work for elements added later), you attach a single listener to a stable parent element and use event.target to figure out which child was actually interacted with. This relies entirely on bubbling and is a standard performance and maintainability pattern for dynamic lists.
- Fewer listeners registered — better memory and setup performance for large lists.
- Automatically works for elements added to the DOM later, since the listener lives on the parent.
- Requires checking
event.target(orevent.target.closest(selector)) to identify the actual element clicked.
Examples
Basic click listener with the event object
const button = document.querySelector('#save-btn');
button.addEventListener('click', (event) => {
event.preventDefault();
console.log('Clicked element:', event.target.tagName);
console.log('Event type:', event.type);
});
Clicked element: BUTTON
Event type: click
Event delegation on a list, handling clicks on any current or future item
const list = document.querySelector('#task-list');
list.addEventListener('click', (event) => {
const item = event.target.closest('.task');
if (!item) return; // click was on the list itself, not an item
item.classList.toggle('done');
console.log('Toggled:', item.textContent);
});
// Even an item added later, e.g. list.appendChild(newLi),
// is handled correctly because the listener is on 'list', not each item.
Toggled: Buy milk and eggs
Common Mistakes
- Attaching a separate listener to every list item and forgetting to attach one to items added dynamically afterward — event delegation solves this by listening on the parent.
- Confusing
event.target(the exact element the event originated on) withevent.currentTarget(the element the listener is actually attached to) inside a delegated handler. - Forgetting to call
event.preventDefault()on a form submit handler, causing an unwanted full-page reload/navigation. - Assuming
stopPropagation()also prevents the default browser action — it only stops the event from bubbling/capturing further;preventDefault()is the one that cancels default behavior.
Exercise
Count clicks on the button and show the count in the paragraph as Clicked 1 times, Clicked 2 times and so on. Attach a single listener to main and act only when the element that was clicked is the button.
Expected result: clicking the button updates the paragraph; clicking the heading or the paragraph does nothing.
// The page contains: <main> with an <h1>, a <p> and a <button>
let count = 0;
const main = document.querySelector("main");
const output = document.querySelector("p");
// TODO: one click listener on main that reacts only to clicks on the button
Show hints
event.targetis the element that was actually clicked.event.target.matches("button")tests whether that element is a button.
Show solution
// The page contains: <main> with an <h1>, a <p> and a <button>
let count = 0;
const main = document.querySelector("main");
const output = document.querySelector("p");
main.addEventListener("click", (event) => {
if (!event.target.matches("button")) return;
count += 1;
output.textContent = "Clicked " + count + " times";
});
Quick quiz
1. What is event bubbling?
- A. An event being cancelled
- B. An event firing twice
- C. An event travelling from the target element up through its ancestors
- D. An event travelling from the document down to the target
Show answer
C. The downward journey is the capturing phase, which listeners can opt into.
2. What does event.preventDefault() do?
- A. Stops the event from reaching other elements
- B. Removes the listener
- C. Reloads the page
- D. Stops the browser's default action, such as following a link or submitting a form
Show answer
D. Stopping propagation is a separate method, event.stopPropagation().
3. Why is addEventListener preferred to an onclick attribute in the HTML?
- A. It keeps behaviour out of the markup and allows several listeners on one element
- B. It is the only one that works
- C. It runs faster
- D. It prevents bubbling
Show answer
A. Listeners can also be removed again with removeEventListener.
Interview questions
What is event delegation and why use it?
Show answer
Instead of attaching a listener to every child element, one listener is attached to a common ancestor. Because events bubble, a click on any child reaches the ancestor, where event.target identifies which child was clicked. It uses one listener in place of hundreds, and it works for children added later, since no listener has to be attached to them. It is the standard way to handle lists and tables.
What is the difference between event.target and event.currentTarget?
Show answer
event.target is the element on which the event originated, the one the user actually clicked. event.currentTarget is the element whose listener is currently running. With a listener on a list and a click on one of its items, target is the item and currentTarget is the list. They are the same only when the listener is on the element that was clicked.
Key Points to Remember
addEventListenersupports multiple handlers per event and accepts options like{ once: true }.- Events travel through capturing (top-down) then bubbling (bottom-up) phases; listeners default to the bubbling phase.
- Event delegation attaches one listener to a parent and uses
event.targetto identify the actual source, which scales to dynamic content. preventDefault()cancels default browser behavior;stopPropagation()stops the event from traveling further.
Practice in the playground
Write and run your own code for this topic, or start from the exercise or an example. Shows a live preview of your HTML, CSS and JavaScript. Your code for each topic is kept in this browser.
Run the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Written by
WebNest Studio Engineering Team
Practical lessons from developers working with Java, Python, React, databases and production systems.
Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering