skip to content

In a DOM click listener registered on a <ul> element, what is the difference between event.target and event.currentTarget, and which of the two changes as the event travels?

level: juniorimportance: must knowfreq 76%

answer

  1. one is fixed, one moves
  2. where it came from vs where you listened
  3. same value in every listener along the path
  4. null after the handler returns
  5. this-binding matches one of the two

basics

~20 s

event.target is the deepest node the event was dispatched on and is identical for every listener in the path. event.currentTarget is the node whose listener is executing right now, so it differs from listener to listener.

solid answer

~40 s

Both answer "which element?", but different questions. `event.target` is where the event originated — for a click, the deepest element under the pointer. It is fixed for the whole dispatch: the listener on the `<li>`, the one on the `<ul>` and the one on `document` all see the same `target`. `event.currentTarget` is the node whose listener is currently executing, so inside the `<ul>` handler it is the `<ul>`, and inside a `document` handler it is `document`. That pair is what delegation runs on: `currentTarget` is the container you attached to, `target` tells you what inside it the user actually hit. One caveat — `currentTarget` is only valid while the handler is on the stack; read it after an `await` or inside a `setTimeout` callback and it is `null`.

code

javascript · 11 lines
javascript
const list = document.createElement('ul');
list.innerHTML = '<li><span>Row</span></li>';
document.body.append(list);

list.addEventListener('click', (event) => {
  console.log('target:', event.target.tagName);
  console.log('currentTarget:', event.currentTarget.tagName);
  console.log('closest li:', event.target.closest('li') === list.firstElementChild);
});

list.querySelector('span').click();

go deeper

for a junior

Be able to say plainly that target is the element the user actually hit and currentTarget is the element your listener sits on, and to show it with a nested span inside a list item.

for a middle

Explain why target is constant across the whole dispatch while currentTarget is set per invocation, and that this in a non-arrow listener equals currentTarget.

for a senior

Show the async trap: currentTarget is cleared once the handler returns, so anything after an await must use a reference captured synchronously. Know when closest() is the right bridge from target to the element you mean.

for a principal

Frame it as an API contract question: when a shared component hands an event to consumer code, decide whether you expose the raw event, the resolved element, or a narrowed payload, and what each choice locks you into.

## Two different questions about "which element" Every running listener involves two elements: the one the event happened *on*, and the one whose listener is *running*. The DOM exposes both, and mixing them up is one of the most common early DOM bugs. - `event.target` — the node the event was dispatched at. For pointer and keyboard events the browser picks the deepest element under the pointer, or the focused element, and that node is the target for the entire dispatch. - `event.currentTarget` — the node whose listener is being invoked at this instant. The DOM sets it just before calling your function and clears it after. Because an event visits many nodes but originates at exactly one, `target` is a constant of the dispatch and `currentTarget` is a variable of it. ## Watching them diverge ```javascript document.querySelector('ul').addEventListener('click', (e) => { console.log('target', e.target.tagName); // e.g. LI console.log('currentTarget', e.currentTarget.tagName); // always UL }); document.addEventListener('click', (e) => { console.log('target', e.target.tagName); // still LI console.log('currentTarget', e.currentTarget); // #document }); ``` Click an `<li>` and both handlers report the same `target`. Their `currentTarget` values differ, because each one reports the element it was itself attached to. ## Why the DOM needs both A single handler on a container serving many descendants is only possible if the handler can ask "what was actually hit?" without being attached to it. `target` answers that. Conversely, code that wants to talk about *its own* element — reading a `data-` attribute off the container, toggling a class on it — needs a stable reference that does not shift with the click position, and that is `currentTarget`. Inside a normal (non-arrow) listener function, `this` is bound to the same value as `currentTarget`, which is why older code writes `this.classList.add(...)`. Arrow functions do not get that binding, so `event.currentTarget` is the portable spelling. ## The gotchas **target is the deepest node, not the useful one.** If the `<li>` contains a `<span>` or an inline `<svg>` icon, the target is that inner node, not the `<li>`. The DOM gives you `Element.closest(selector)` to walk up from the target to the element you actually care about — `event.target.closest('li')`. **currentTarget is cleared after dispatch.** The property is only set while the listener is executing. This is the classic failure: ```javascript button.addEventListener('click', async (e) => { await fetch('/save'); e.currentTarget.disabled = false; // TypeError: currentTarget is null }); ``` The fix is to capture the node synchronously (`const el = e.currentTarget;`) before the first `await`. `event.target` survives, because it is not reset — but `target` is not necessarily the element you attached to. **Neither is the same as `relatedTarget`.** On `mouseover`/`mouseout` and `focusin`/`focusout`, `event.relatedTarget` names the *other* element involved — the one being left, or the one being entered. It answers a third question and is a separate property. ## How to say it in an interview One sentence carries it: *target is where the event came from and is the same for every listener; currentTarget is where this listener is attached and changes as the event moves through the tree.* Then give the delegation framing — the container is `currentTarget`, the clicked thing is `target` — and mention that `currentTarget` is null once the handler returns, because that detail signals you have actually debugged it rather than only read about it.

  • Inside a listener attached with addEventListener, what is `this`, and when does that stop being true?
    In a normal function listener `this` is bound to the same node as `event.currentTarget`. In an arrow function it is not — arrows take `this` from the enclosing scope, so `this` is whatever it was where the arrow was written. That is why `event.currentTarget` is the safe spelling in modern code.
  • Why does reading event.currentTarget after an await inside an async listener give you null?
    The DOM sets `currentTarget` immediately before invoking a listener and clears it when that invocation returns. An `await` returns control to the dispatch machinery, so by the time the continuation runs the property has been reset. Capture the node in a local variable before the first `await`.
  • What does event.relatedTarget mean, and on which events is it populated?
    It names the *other* element in a transition: on `mouseover` it is the element the pointer left, on `mouseout` the one it entered, and on `focusin`/`focusout` the element losing or gaining focus. It is null when the pointer or focus came from outside the document.

saying these in an interview costs you the question

  • Saying target and currentTarget are interchangeable
  • Claiming target updates to each node as the event moves
  • Reading currentTarget after an await and expecting the element
  • Assuming target is always the element you attached the listener to
  • Confusing relatedTarget with target on mouseover

context