event handlers
garbage collection
memory management
programming
software development

Do event handlers stop garbage collection from occurring?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Event handlers do not stop garbage collection globally, but they can keep specific objects alive longer than expected. The issue is not the garbage collector itself. The issue is that an event subscription can create a reference chain that keeps an otherwise unused object reachable.

Why Event Subscriptions Affect Lifetime

A garbage collector frees objects that are no longer reachable from application roots. When one object subscribes to events published by another, the publisher often stores a delegate or callback reference, and that reference may point back to the subscriber.

In C#, a typical subscription looks like this:

csharp
1public class Publisher
2{
3    public event EventHandler? Changed;
4
5    public void Raise()
6    {
7        Changed?.Invoke(this, EventArgs.Empty);
8    }
9}
10
11public class Subscriber
12{
13    public Subscriber(Publisher publisher)
14    {
15        publisher.Changed += HandleChanged;
16    }
17
18    private void HandleChanged(object? sender, EventArgs e)
19    {
20        Console.WriteLine("Changed");
21    }
22}

If Publisher lives for a long time, it keeps the delegate list alive. That delegate references the subscriber instance, so the subscriber remains reachable and cannot be collected yet.

The Garbage Collector Is Still Working Correctly

This point matters: event handlers do not block or disable garbage collection. The collector is doing the right thing. It sees a valid reference path, so it treats the subscriber as live memory.

A useful mental model is:

  1. A long-lived publisher stores a callback.
  2. The callback points to a method on a subscriber.
  3. The subscriber is still reachable through that callback.
  4. Because it is reachable, the collector keeps it.

If the publisher itself becomes unreachable, then the whole graph can usually be collected together.

A Real Leak Pattern in C#

The classic leak appears when a long-lived service publishes events and short-lived UI objects subscribe.

csharp
1public sealed class Dashboard : IDisposable
2{
3    private readonly TimerService _timerService;
4
5    public Dashboard(TimerService timerService)
6    {
7        _timerService = timerService;
8        _timerService.Tick += OnTick;
9    }
10
11    private void OnTick(object? sender, EventArgs e)
12    {
13        Console.WriteLine("Refreshing UI");
14    }
15
16    public void Dispose()
17    {
18        _timerService.Tick -= OnTick;
19    }
20}

If TimerService is effectively a singleton and old Dashboard instances are never unsubscribed, they accumulate even after the UI no longer needs them.

That is why event-heavy C# code often pairs subscriptions with Dispose or another explicit cleanup step.

Browser Event Listeners Have the Same Risk

JavaScript uses different runtime internals, but the reachability issue is similar. A DOM node or other event target can keep a callback alive, and that callback can close over other objects.

javascript
1function attachSaveHandler(button, model) {
2  function handleClick() {
3    console.log(model.id);
4  }
5
6  button.addEventListener("click", handleClick);
7
8  return () => {
9    button.removeEventListener("click", handleClick);
10  };
11}

If the listener is never removed and the target remains reachable, the closure may keep model in memory longer than intended.

When Event Handlers Are Harmless

An event subscription is not automatically a leak. If the publisher and subscriber have the same lifetime, the collector can often reclaim both at the same time.

For example, if a short-lived component creates a DOM element, attaches a listener, and then the entire component tree becomes unreachable together, there may be no practical leak.

The risky cases usually involve lifetime mismatches, such as:

  • a global singleton publisher
  • a cache or service that lives for the entire app session
  • frequently created UI views that subscribe but never detach
  • anonymous handlers that are difficult to remove later

Safer Cleanup Patterns

The most direct fix is explicit unsubscription.

csharp
publisher.Changed -= HandleChanged;

And in browser code:

javascript
button.removeEventListener("click", handleClick);

For more structured cleanup, modern browser code can use AbortController:

javascript
1const controller = new AbortController();
2
3button.addEventListener("click", onClick, {
4  signal: controller.signal
5});
6
7controller.abort();

In .NET, weak event patterns are another option when you want the subscription not to keep the subscriber alive strongly.

Common Pitfalls

A common mistake is assuming the garbage collector will notice that an object is no longer useful and remove it anyway. If an event publisher still references it, the collector cannot safely do that.

Another pitfall is subscribing with inline lambdas and later having no direct handle for unsubscription.

Captured state can also hide the real memory cost. A tiny event handler may keep a much larger object graph alive through its closure.

Finally, memory leaks from event subscriptions often appear only after repeated navigation or repeated object creation, so they can be missed by simple manual testing.

Summary

  • Event handlers do not stop garbage collection, but they can keep objects reachable.
  • The leak risk comes from reference chains between publishers, delegates, and subscribers.
  • Long-lived publishers and short-lived subscribers are the classic problem.
  • Unsubscribe explicitly when object lifetimes differ.
  • Both C# events and JavaScript listeners need cleanup discipline to avoid memory leaks.

Course illustration
Course illustration

All Rights Reserved.