Delegating to document is the version of event delegation that always works, so it becomes the default. Thirty handlers later, every click anywhere on the page walks the full path to the root and runs thirty selector matches on the way — and that is the cheaper of the two problems.
// every click in the page is tested against all three
$(document).on('click', '.js-remove-line', removeLine);
$(document).on('click', '.js-apply-coupon', applyCoupon);
$(document).on('click', '.js-open-size-guide', openSizeGuide);
// bound to a container that outlives the content it holds
$('#basket').on('click', '.js-remove-line', removeLine);
The matching cost is invisible on a click and measurable the moment someone delegates mousemove or scroll the same way. The expensive problem is the other one: any handler between the target and document that calls stopPropagation() silently disables every document-level delegation below it, and the symptom is a button that works everywhere except inside one widget written by someone else. Nothing throws, nothing logs, and the widget looks innocent. Delegate to the nearest ancestor that outlives the content — a section wrapper, a table, a modal root — and keep document for the handlers that genuinely have no container, such as a global keyboard shortcut.