Comb Inheritance
This article is a re-posting of the original located at: https://thenewobjective.com/types-and-programming-languages/comb-inheritance/
Introduction
Peter Norvig has famously said that "Design patterns are bug reports against your programming language". A popular design pattern still in use today is the chain of responsibility pattern, which allows an object to pass a request along a chain of potential handlers who can process and/or pass the request further along the chain. The key goal here being that the sender of the request does not need to know which handler will ultimately process the request, enabling loose coupling between the sender and the handlers.
Despite the loose-coupling benefits, to implement this pattern in a traditional object-oriented programming language often requires a fair amount of boilerplate code to set up the chain of handlers and the logic for passing requests along the chain. However, in JavaScript, we can leverage its prototypal inheritance model plus Proxies to enable a more elegant solution known as comb inheritance.
The Chain of Responsibility Pattern
Objects are intended to be self-contained units of behavior and state. However, in many scenarios, an object may not be able to handle a request on its own. For example, consider a logging system where different loggers handle messages of varying severity levels. In these cases, the chain of responsibility pattern allows an object to delegate the request to the next object in a chain until one of the objects can handle it.
Here is how a chain of responsibility is structured:
Sequentially, when a Sender sends a Request, it invokes the handle method on its associated Handler. If the Handler can process the Request, it does so and returns a Response. If not, it forwards the Request to its nextHandler.
Logger Example
As a practical example, consider a logging system with three handlers: InfoHandler, WarningHandler, and ErrorHandler. Each handler is responsible for processing messages of a specific severity level. If a handler cannot handle a message, it passes the message to the next handler in the chain. The Logger acts as the Sender and exposes a natural log() action.
Comb Inheritance
The Logger example above illustrates the chain of responsibility concept well, but notice how much boilerplate is required: abstract base classes, explicit handle() methods, canHandle() and process() abstractions, and manual chain setup. What if we could make this pattern more implicit and natural?
Enter Comb Inheritance, a form of multiple inheritance pioneered by NewtonScript, the programming language developed for Apple's Newton platform in the 1990s. The name "comb" comes from the visual appearance when you diagram the inheritance structure - it looks like a comb with two distinct paths (or "teeth"):
Here's how the two paths work together:
When looking up a property on Child:
This creates a powerful composition pattern where objects can inherit both from type hierarchies (prototypes) and from contextual relationships (parents).
Implementing Comb Inheritance in JavaScript
JavaScript's prototypal inheritance naturally handles the proto path, so we only need to add the parent path. We can achieve this simply enough using a Proxy to intercept property lookups:
What's notable in this implementation is that accessing parent[prop] automatically re-enters the proxy's get trap, handling the entire parent chain recursively without explicit loops.
Revisiting the Logger Example
With Comb Inheritance, our logger chain becomes dramatically simpler:
Notice how we've eliminated:
The chain of responsibility is now implicit in the inheritance structure itself. When super.log() is called and the method doesn't exist on the prototype, the Proxy intercepts and delegates to the parent instance, seamlessly implementing the pattern.
Document Hierarchies
Comb Inheritance shines in scenarios where objects need both type-based and context-based behavior:
Consider a document system where pages inherit both formatting rules from their template class (prototype chain) and specific overrides from their container hierarchy (parent chain):
This example showcases both "teeth" of the comb:
Provides leftMargin, rightMargin, bottomMargin (default 1.0), paperSize (default 'letter'), and getMargins() method
Provides headerText and footerText from specific document instances
Event Propagation
A UI framework where events naturally bubble up the component hierarchy without explicit listener registration:
The event system leverages both inheritance paths:
Prototype chain: handleEvent() method is shared across all UIComponent instances via prototype
Parent chain: Events automatically propagate up the component hierarchy (saveButton → sidebar → mainWindow)
This eliminates the need for explicit event listener registration, event emitters, or manual propagation logic. The UI hierarchy itself defines the event propagation path.
Conclusion
Comb Inheritance demonstrates how we can eliminate the need for a number of Object-Oriented design patterns as separate constructs. By making the chain of responsibility implicit in the inheritance mechanism itself, we reduce boilerplate, improve clarity, and make our code more maintainable.
While NewtonScript introduced this concept in the 1990s, JavaScript's dynamic nature and Proxy support make it a possible to reintroduce this for modern applications. As we've seen through the logger, document hierarchy, and event propagation examples, this pattern naturally fits scenarios where objects need both type-based behavior (via prototypes) and context-specific delegation (via parent instances).
The implementation is available on NPM at @mlhaufe/comb-inheritance, complete with tests for all examples presented in this article.
References and Further Reading