In this article
  1. Before you start
  2. Why interviewers ask this
  3. Event aggregator from first principles
  4. Worked example
  5. Trade-offs and edge cases
  6. How to answer it in an interview
  7. Common mistakes
  8. Follow-up questions
  9. Sources

Before you start

This page is one step of the machine coding rounds learning path. It assumes you can read short Python.

Why interviewers ask this

A candidate who builds an event aggregator without a leak or a skipped handler shows they can reason about object lifespan, not only about syntax. The task is a small design-pattern exercise of the kind found in low-level design interview questions and in machine coding rounds. No public source used for this page reports a specific company asking it, so what follows is a description of what the task tests.

A weak answer writes a class with a dictionary and a publish method, then stops. A strong answer explains why clients should subscribe in one place, shows how a subscription is removed, and says what the pattern does not solve. A Staff-level answer separates the aggregator from a mediator and from a message broker. It also says what happens when a handler changes the subscriber list while an event is being delivered.

Event aggregator from first principles

Event aggregator
An event aggregator is a single object that sits between many event sources and many observers, subscribes to the events of the sources, and propagates them to its own subscribers, so a client registers in one place instead of with every source. It forwards events and holds no business logic.
Subscription token
A subscription token is a value that an aggregator returns when a handler subscribes and that the caller later passes back to remove exactly that handler, so one subscription can be undone without disturbing the others that listen to the same event.
Lapsed listener
A lapsed listener is an object that no longer does useful work but is still registered as a handler somewhere, so the registry holds a reference to it and the object cannot be reclaimed until it is unsubscribed; the result is a memory leak that grows with every listener left behind.

In plain words

Think of one notice board in an office lobby. You do not walk to every department to ask for news. You read the board, and each department pins its news there. The analogy breaks in one place. A real board keeps its notices. An aggregator stores no events and passes each one to whoever is registered at that moment.

In one precise sentence, an event aggregator is a registry of handlers keyed by event type, plus a publish operation that calls the handlers for that type.

Martin Fowler gives its purpose as to "channel events from multiple objects into a single object to simplify registration for clients" (Fowler, 2004). Without it, a client must find and register with each source object individually. A source with several events needs one subscription per event. Fowler calls the aggregator "a simple element of indirection."

A facade is a single object that offers a simple front to a more complicated group of objects. Fowler suggests you can think of the aggregator as "a particular form of Facade that focuses only on observer relationships." It is built on the observer pattern, so every danger of observer still applies.

Step 1: Create one shared instance

The aggregator is one shared instance that holds a registry of handlers. The registry is a dictionary from an event type, such as the text "order.created", to a list of handlers. Input: nothing. Output: an empty registry inside one object that the whole program shares.

A singleton is a class that allows only one instance and gives every caller access to it. The Prism Library documentation describes a multicast publish/subscribe aggregator, meaning several publishers can raise the same event and several subscribers can listen to it. Francis Cheung's Prism post says to treat the aggregator as a singleton; the reason is that publisher and subscriber must use the same instance to find each other. In an interview you can pass one instance into every constructor instead of creating a global. The sharing is what matters. You now have a place to register; the next step fills it.

Step 2: Subscribe returns a token, and unsubscribe must use it

Subscribing adds a handler to the list for one event type and returns a subscription token. Input: an event type and a function. Output: the token. Subscribers register with the aggregator, not with each source. Unsubscribing takes the same event type and token and removes exactly that handler.

Skipping the unsubscribe call is the classic error. The registry holds a reference to every handler, so a finished listener stays alive and becomes a lapsed listener. Microsoft's weak event patterns page describes the same mechanism for ordinary events: listening creates a strong reference from the source to the listener. Unless the handler is explicitly unregistered, the listener's lifespan is influenced by the source's lifespan. Here the source is the aggregator, which usually lives as long as the program.

Prism documents strongly referenced delegates with manual unsubscribing as the option when you have performance concerns, and it holds subscribers weakly by default. Python's weakref module offers a weak variant, at the cost of handlers vanishing when nothing else references them. A weak reference to a lambda dies at once, because nothing else holds the lambda, and a bound method needs weakref.WeakMethod, because a plain weak reference to a bound method also dies at once. The rule for an answer: every subscribe has a matching unsubscribe when the listener's work ends.

Step 3: Publish looks up the handlers and calls them

A source publishes an event to the aggregator, which looks up the handlers and calls them. Input: an event type and a payload. Output: the number of handlers called, which is zero when nobody listens. The publisher does not check for subscribers. Derick Bailey's 2013 comparison of the aggregator and the mediator, which is practitioner opinion, says the object triggering the event does not care if there are any subscribers.

Copy the handler list before looping over it. A handler may unsubscribe itself, or subscribe a new handler, in the middle of delivery. If the loop walks the live list, removing an item shifts the rest and a neighbor can be skipped. The worked example shows a skipped handler. Whether one failing handler should stop the others is a design choice that none of the sources here settles. Say which you chose: catch and log, or let the error rise.

Step 4: Filter or generalize what subscribers receive

An optional filter or generalizing step narrows or widens what subscribers receive. A filter is a predicate that the subscriber supplies, and the aggregator calls the handler only when it returns true. Prism offers this: "If you need to filter an event, provide a filter delegate when subscribing."

Generalizing goes the other way. Fowler notes that an aggregator can convert events specific to a source object into a more generic event. Observers then register for fewer event types, but they are notified of events that may not matter to them. His own advice is: "Most of the time, I'd suggest using just a single coarse-grained event." In his example the aggregator re-raises two specific consultant events and one general change event, so a client listening only to the general event is called once for each source event.

One aggregator sits between sources and subscribersBoxes and arrows: an aggregator holds a registry of handlers, subscribers register with it, a source publishes events to it, the aggregator delivers each event to the registered handlers, and an optional filter sits on the delivery path.Eventaggregatorone sharedinstanceHandlerregistryevent type tohandlerssubscribeSubscriber ASubscriber BpublishdeliverEventsourceFilter orgeneralizeDiagram: Interview Clozo, CC BY 4.0 ·interview.clozo.ai/blog/event-aggregator-interview-question.html

Subscribers and sources never meet; the aggregator holds the registry and forwards each event to the handlers registered for it.CC BY 4.0 — source: Event aggregator interview question: how do you build one?
Step-by-step description
  1. One aggregator holds a registry of handlers.
  2. Subscribers register with the aggregator.
  3. A source publishes; the aggregator calls the handlers.
  4. An optional filter on the delivery path filters or generalizes events.
  • The aggregator is one shared instance that holds a registry of handlers.
  • Subscribers register with the aggregator, not with each source.
  • A source publishes an event to the aggregator, which looks up the handlers and calls them.
  • An optional filter or generalizing step narrows or widens what subscribers receive.

Knowing it is half the job. Saying it is the other half.

Interview Clozo is an AI interview copilot for real interview rounds: it hears the question and puts a strong answer on your screen, and you say it in your own words. See the features or how it works.

Start Free Trial →

Worked example

The class below is a complete answer for a round. Subscribe is O(1) amortized, publish is O(k) for the k handlers of that event type (the copy is also O(k)), and unsubscribe is O(k).

class EventAggregator:
    def __init__(self):
        self._subs = {}   # event type -> list of (token, handler)
        self._next = 0

    def subscribe(self, event_type, handler):
        self._next += 1
        self._subs.setdefault(event_type, []).append((self._next, handler))
        return self._next

    def unsubscribe(self, event_type, token):
        self._subs[event_type] = [
            s for s in self._subs.get(event_type, []) if s[0] != token]

    def publish(self, event_type, payload=None):
        calls = 0
        for _, handler in list(self._subs.get(event_type, [])):
            handler(payload)
            calls += 1
        return calls

The trace uses illustrative event names. Handlers A and B subscribe to "order.created" and handler C to "order.shipped". Publishing "order.created" calls 2 handlers, publishing "order.shipped" calls 1, and publishing "order.cancelled" calls 0 because nobody listens. After B unsubscribes, publishing "order.created" again calls 1. In total 4 handlers ran, in the order A, B, C, A.1

The failure cases are also counted. Three listeners that subscribe to one event and are never unsubscribed leave 3 handlers in the registry; unsubscribing each token returns the count to 0. Two separate aggregator instances do not share handlers: a handler subscribed on one is reached 0 times by a publish on the other, which is why the shared instance matters. When a handler removes itself from a live list during delivery, 2 of 3 handlers run; looping over a copy runs all 3.1

Now Fowler's example. A Consultant raises two events, LocationChanged and AvailabilityChanged, and his aggregator re-raises a specific event for each plus a general ConsultantChanged. One movement that changes both location and availability produces 4 notifications: 2 specific ones and the general one twice, once per source event.1

For illustrative inputs of 100 sources, 2 events each and 5 clients, direct wiring needs 1,000 subscriptions and an aggregator needs 205.
Quantity (illustrative inputs, computed)Value
Direct subscriptions, 10 sources, 2 events, 5 clients (illustrative)100
Through an aggregator, same system25
Direct subscriptions, 100 sources1,000
Through an aggregator, 100 sources205
Direct subscriptions, 1,000 sources10,000
Through an aggregator, 1,000 sources2,005
Handlers run in the order-event trace4
Notifications for one two-event movement (Fowler's example)4

The formulas come from Fowler's description of registration, not from a quoted figure. Without an aggregator each of C clients registers with each of N sources for each of E events, which is C times N times E subscriptions. With one, the aggregator subscribes to every source event, N times E, and each client subscribes once to it, C more.1

With 100 sources, 2 events each and 5 clients (illustrative inputs), direct wiring needs 1,000 subscriptions. Through an aggregator the same system needs 205 subscriptions.1

An aggregator cuts subscriptions as sources multiplyGrouped bars of subscriptions needed for 10, 100 and 1,000 sources with 2 events each and 5 clients: direct wiring needs 100, 1,000 and 10,000, an aggregator needs 25, 205 and 2,005.Subscriptions05,00010,000101001,000Number of sourcesDirect subscriptionsVia aggregator1001,00010,000252052,005Diagram: Interview Clozo, CC BY 4.0 ·interview.clozo.ai/blog/event-aggregator-interview-question.html
Direct subscriptions grow with sources times clients; through an aggregator they grow with sources plus clients.CC BY 4.0 — source: Event aggregator interview question: how do you build one?
Text description of the diagram
  • With 100 sources, 2 events each and 5 clients, direct wiring needs 1,000 subscriptions.
  • Through an aggregator the same system needs 205 subscriptions.

Trade-offs and edge cases

Aggregator or mediator?

An aggregator only forwards events, while a mediator holds coordination logic. A mediator is an object that encapsulates the logic of coordinating several other objects to reach a goal. Bailey's post, which is opinion rather than a standard, says an aggregator has no logic other than forwarding events from a publisher to a subscriber. If your class decides who may do what, or sequences calls, it has become a mediator. Say that out loud in the round.

Fowler's view is consistent with this. He treats the aggregator as indirection over observer relationships and not as a place for rules. If you are tempted to add ordering or business checks inside publish, move them into a separate coordinator and keep the aggregator thin.

Aggregator or message broker?

An aggregator works between objects inside one program, while a message broker carries messages between programs. Fowler's pattern is object-level and built on observer. A broker adds a network, delivery semantics to design for, and failure modes such as lost or repeated messages, which an in-process call does not have. The same vocabulary of publishers and subscribers appears in both, so interviewers may check that you can tell them apart. For stream-shaped tasks see event and log processing problems.

An aggregator forwards, a mediator coordinates, a broker crosses programsTable comparing an event aggregator, a mediator and a message broker by where each runs, whether it holds logic, and how it delivers.QuestionAggregatorMediatorMessagebrokerWhere it runsinside oneprogramusually oneprogrambetweenprogramsHolds logicno, forwardsonlyyes,coordinatesno, movesmessagesDeliverya functioncallfunctioncallsnetworkmessagesDiagram: Interview Clozo, CC BY 4.0 ·interview.clozo.ai/blog/event-aggregator-interview-question.html
An aggregator forwards, a mediator coordinates, and a broker carries messages between programs.CC BY 4.0 — source: Event aggregator interview question: how do you build one?
Text description of the diagram
  • An aggregator only forwards events.
  • A mediator holds coordination logic.

What the aggregator does not fix

An aggregator centralizes registration, but it does not remove the observer pattern's problems. Fowler says that because it is based on observer, all the danger areas with observer need attention. Delivery order, re-entrancy and handler errors remain yours to define. A shared instance is also a hidden global, which makes tests harder unless you create a fresh aggregator per test.

Fine-grained events are not always better. Many event types mean many subscriptions, and generalizing gives fewer registrations but extra notifications. For interface code, where many widgets raise small events, the same trade appears in frontend interview questions. Cache-style tasks that expire entries, such as time-based key-value stores, can publish an event when an entry expires. The circuit breaker pattern can announce its state changes the same way, and the in-memory database assessment is a related machine-coding task.

How to answer it in an interview

Interviewer asks

Design an event aggregator.

A strong answer

An event aggregator is one shared object between event sources and subscribers. It keeps a dictionary from event type to a list of handlers. Subscribe adds a handler and returns a token; unsubscribe removes that handler by token, because otherwise the aggregator keeps finished listeners alive. Publish copies the list for that event type and calls each handler, so a handler can unsubscribe during delivery safely. It forwards and holds no business logic; if it needed that, it would be a mediator. I would ask about threads and error handling before adding locks.

The spoken structure: define it in a sentence, name the three operations, then the leak and the copy, then the line between aggregator and mediator.

Common mistakes

  • "I never need to unsubscribe, the object goes away." (wrong) The registry holds a reference, so the listener stays alive. (right) "Every subscribe returns a token, and the listener unsubscribes when it finishes."
  • "I created a new aggregator in each class." (wrong) Publisher and subscriber then use different instances and never meet. (right) "One shared instance, passed in or exposed as a singleton."
  • "I loop over the live handler list in publish." (wrong) A handler that unsubscribes mid-delivery makes a neighbor get skipped. (right) "I loop over a copy of the list."
  • "The aggregator decides which handler goes first and checks permissions." (wrong) That is coordination logic, so it is a mediator. (right) "The aggregator only forwards; a separate coordinator holds the rules."

Follow-up questions

What is the difference between an event aggregator and a mediator?

An event aggregator forwards events from publishers to subscribers and holds no logic, while a mediator holds the logic that coordinates several objects toward a goal. The aggregator's publisher is fire-and-forget, as Bailey's post puts it; a mediator knows what should happen next.

Is an event aggregator the same as an event bus or message broker?

No. Fowler's pattern is an in-process object built on observer. A message broker is a separate system between programs, with network and delivery concerns. Informally some teams call a bus-based design an aggregator, but the pattern as documented is object-level.

How do you avoid memory leaks with an event aggregator?

Return a subscription token from subscribe and unsubscribe when the listener finishes. If listeners cannot know when to stop, use weak references so the aggregator does not keep them alive, as Prism does by default and as Microsoft's weak event pattern describes for ordinary events.

How do you make an event aggregator thread-safe?

No source used here sets the rule, so this is design discussion. A common approach guards the registry with a lock, copies the handler list while holding it, and calls handlers outside the lock so a slow handler does not block subscribing. Prism also lets a subscriber choose which thread receives the event.

Why should an event aggregator be a singleton?

Publisher and subscriber must use the same instance to find each other, which is why Prism guidance treats the aggregator as a singleton. Passing one instance through constructors gives the same sharing without a global.

What changes with thousands of handlers on one event type?

Every publish copies the handler list, so its cost grows with the number of handlers k. A copy-on-write design avoids that: store the handlers as an immutable tuple, replace it on subscribe and unsubscribe, and let publish loop over the current tuple without copying. Subscribing then costs more, publishing less.

Key takeaways
  • An event aggregator is one shared object that forwards events from many sources to many subscribers.
  • Clients register in one place, so subscriptions grow with sources plus clients, not sources times clients.
  • Subscribe returns a token and unsubscribe uses it, or finished listeners stay alive.
  • Publish loops over a copy of the handler list so handlers can unsubscribe safely.
  • The aggregator forwards and holds no logic; coordination logic makes it a mediator.

Sources

  1. Event Aggregator (Further EAA development) — martinfowler.com (Martin Fowler), 29 Sep 2004
  2. Event Aggregator (Prism Library documentation) — Prism Library, 26 Oct 2026
  3. Weak event patterns — Microsoft Learn, 26 Oct 2026
  4. Decoupled Communication with Prism (Event Aggregation) — Microsoft Learn archive (Francis Cheung), 26 Oct 2026
  5. Event Aggregator And/Or/vs Mediator: A Tale Of Two Patterns — Los Techies (Derick Bailey), 18 Mar 2013