Web APIsJavaScript

What is the difference between mouseenter and mouseover?

The short answer

The key difference is that mouseover bubbles and mouseenter does not. This means mouseover fires when the pointer enters an element or any of its children, while mouseenter only fires once — when the pointer enters the element's own boundary. In practice, mouseenter is what you usually want. mouseover fires far more often than you'd expect because of bubbling.

What bubbling means here

Let's say you have a card with some child elements:

<div id="card">
<h2>Title</h2>
<p>Description</p>
</div>

If you listen for mouseover on the card:

document
.getElementById('card')
.addEventListener('mouseover', () => {
console.log('mouseover fired');
});

You might expect this to fire once when your mouse enters the card. But it actually fires multiple times — once when you enter the card, again when your mouse moves over the <h2>, again when it moves from the <h2> to the <p>, and so on. Every time you cross a child element boundary, mouseover fires on that child and then bubbles up to the card.

Now compare with mouseenter:

document
.getElementById('card')
.addEventListener('mouseenter', () => {
console.log('mouseenter fired');
});

This fires exactly once: when the pointer enters the card's boundary. Moving between the <h2> and <p> inside the card doesn't trigger it again, because mouseenter doesn't bubble. It only cares about the element it's attached to.

Seeing the difference clearly

Here's a concrete example that makes the behavior obvious:

const card = document.getElementById('card');

let overCount = 0;
let enterCount = 0;

card.addEventListener('mouseover', () => {
overCount++;
console.log('mouseover count:', overCount);
});

card.addEventListener('mouseenter', () => {
enterCount++;
console.log('mouseenter count:', enterCount);
});

Move your mouse into the card, hover over the title, then the description, then back out. You'll see mouseenter fires once while mouseover might fire five or six times. The more child elements inside the container, the bigger the gap.

The paired exit events

Each "enter" event has a corresponding "leave" event, and the same bubbling distinction applies:

  • mouseout bubbles — fires when the pointer leaves an element or moves to a child element
  • mouseleave does not bubble — fires once when the pointer leaves the element's boundary
card.addEventListener('mouseout', () => {
console.log('mouseout'); // fires when moving between children too
});

card.addEventListener('mouseleave', () => {
console.log('mouseleave'); // fires only when pointer actually leaves the card
});

So the pairs are:

Enter eventExit eventBubbles?
mouseovermouseoutYes
mouseentermouseleaveNo

When to use which

Use mouseenter / mouseleave when you care about the pointer entering or leaving a specific element as a whole. This covers most common cases:

  • Showing a tooltip when hovering over a button
  • Highlighting a card on hover
  • Showing a dropdown menu when hovering over a nav item
const tooltip = document.getElementById('tooltip');

button.addEventListener('mouseenter', () => {
tooltip.style.display = 'block';
});

button.addEventListener('mouseleave', () => {
tooltip.style.display = 'none';
});

Use mouseover / mouseout when you need event delegation. Since mouseenter doesn't bubble, you can't use it with the delegation pattern (attaching one handler to a parent to handle events from multiple children). mouseover bubbles, so it works:

list.addEventListener('mouseover', (event) => {
const item = event.target.closest('li');
if (item) {
item.classList.add('highlighted');
}
});

list.addEventListener('mouseout', (event) => {
const item = event.target.closest('li');
if (item) {
item.classList.remove('highlighted');
}
});

If you tried this with mouseenter, the parent <ul> would never receive the event because it doesn't bubble up from the <li> elements.

The relatedTarget property

Both event types provide event.relatedTarget, which tells you where the pointer came from (on enter/over) or where it's going (on out/leave). This can help you filter out the noisy child-boundary crossings when using mouseover:

card.addEventListener('mouseover', (event) => {
if (!card.contains(event.relatedTarget)) {
console.log(
'Pointer truly entered the card from outside'
);
}
});

This check effectively makes mouseover behave like mouseenter. But at that point, you might as well just use mouseenter directly.

Why interviewers ask this

This question tests whether you understand event bubbling in a practical context. It's easy to say "I know what bubbling is" in the abstract, but explaining how it affects something as common as hover behavior shows you've dealt with real UI interactions. It also reveals whether you'd reach for the right tool — most hover effects should use mouseenter/mouseleave, and knowing when you'd actually need mouseover/mouseout (delegation) shows thoughtful decision-making.