This morning, before writing up my post on how to unbind Scope.$on() event handlers in AngularJS, I took a look at the AngularJS source code. And, much to my delight, I discovered that the implementation of Scope.$broadcast() was much more efficient than I had imagined it would be. My assumption was that an event would be broadcast to the entire scope-tree; but, as it turns out, the event is only broadcast to branches that have matching event bindings.
If you look at the Scope.$on() implementation, you will see that when an event binding is registered for a given event type, AngularJS increments a counter for that event type on the current scope. AngularJS then travels up the scope hierarchy and increments the same counter on each parent scope. This means that the existence of a deeper event binding can be determined using the counter value at a shallower point in the scope tree.
On the flip side, the Scope.$broadcast() method then looks at the relevant counters and only walks down into subtrees of the scope hierarchy that have matching event bindings. So, in other words, when you broadcast an event, AngularJS only traverses parts of the scope tree that actually have matching event bindings and ignores subtrees that do not have matching event bindings.