Mixins & Composition Interview Questions & Answers
23 questions Updated 2026-06-18
JavaScript mixins and composition interview questions — sharing behavior without inheritance, mixin factories, composition over inheritance, and duck typing.
Read the in-depth guideJavaScript Mixins & Composition — Sharing Behavior Beyond Single InheritanceA mixin is a reusable bundle of methods that can be added to multiple classes or objects without using inheritance — a way to share behavior across unrelated types.
const serializable = {
serialize() { return JSON.stringify(this) },
}
class User {}
Object.assign(User.prototype, serializable) // mix in
new User().serialize()
Mixins solve "I need this capability in several classes that don't share a common parent." They're JavaScript's answer to multiple inheritance and interface-style sharing.
Copy the mixin's methods onto a class's prototype (shared) or onto an instance.
const canFly = { fly() { return `${this.name} flies` } }
const canSwim = { swim() { return `${this.name} swims` } }
class Duck { constructor(name) { this.name = name } }
Object.assign(Duck.prototype, canFly, canSwim)
const d = new Duck('Donald')
d.fly(); d.swim()
Object.assign(Class.prototype, ...mixins) adds the methods once for all
instances. The mixin methods use this, so they operate on whatever object they
end up on.
A function that takes a base class and returns a new class extending it with added behavior — composable via nesting.
const Timestamped = (Base) => class extends Base {
constructor(...args) { super(...args); this.createdAt = Date.now() }
}
const Serializable = (Base) => class extends Base {
toJSON() { return { ...this } }
}
class Model extends Timestamped(Serializable(Object)) {}
Each factory wraps the previous class, building a linear chain that combines
features — the idiomatic modern mixin pattern, since it works with super and
extends.
Build objects by combining small, focused pieces (functions, mixins, held objects) rather than inheriting from a deep class hierarchy — more flexible and less coupled.
// inheritance: rigid hierarchy
class Robot extends Machine {}
// composition: assemble capabilities
const robot = { ...withPower(), ...withMovement(), ...withSensors() }
Inheritance locks a type into one hierarchy and suffers the fragile-base-class problem. Composition lets you mix exactly the behaviors needed and swap them independently. It's a core OO design principle, especially apt in JS.
Combining simple functions so the output of one feeds the next, building complex behavior from small reusable parts.
const compose = (...fns) => (x) => fns.reduceRight((acc, fn) => fn(acc), x)
const pipe = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc), x)
const process = pipe(trim, toLowerCase, removeSpaces)
process(' Hello World ') // 'helloworld'
compose applies right-to-left, pipe left-to-right. Function composition is
the functional-programming counterpart to object composition — assemble behavior
from tiny, testable units.
"If it walks like a duck and quacks like a duck, it's a duck" — judging an object by whether it has the needed methods/properties, not by its class.
function makeItQuack(thing) {
if (typeof thing.quack === 'function') return thing.quack()
throw new Error('not quackable')
}
JavaScript leans heavily on duck typing: code checks for capabilities
(typeof x.then === 'function' for thenables) rather than instanceof. It pairs
naturally with composition and mixins, where behavior matters more than lineage.
Use a mixin when a capability is shared by unrelated classes, or when a class needs behaviors from multiple sources (which single inheritance can't provide).
// both need serialization, but aren't otherwise related
Object.assign(User.prototype, Serializable)
Object.assign(Invoice.prototype, Serializable)
Use inheritance for a genuine is-a relationship with a single, stable parent. If you find yourself wanting to extend two parents, that's a mixin (or composition) situation.
With Object.assign, the last mixin wins silently — no error, the earlier
method is overwritten.
const a = { run() { return 'a' } }
const b = { run() { return 'b' } }
Object.assign(target, a, b)
target.run() // 'b' — collision resolved by order, silently
You must manage conflicts yourself: rename methods, namespace them, or resolve explicitly. This is the trade-off mixins make for flexibility — no automatic conflict detection like some languages' traits.
Method-only mixins are simplest. For state, use a mixin factory (so it can run
constructor logic via super) or initialize state in the mixin's methods —
object-literal mixins can't easily set up per-instance fields.
const Counter = (Base) => class extends Base {
#count = 0
increment() { return ++this.#count }
}
class Widget extends Counter(Object) {}
The class-factory form integrates with the constructor chain, so it can declare
fields and call super(). Plain Object.assign mixins are best for stateless
behavior.
Holding another object and forwarding calls to it (has-a), instead of inheriting — a flexible alternative to subclassing.
class Engine { start() { return 'vroom' } }
class Car {
#engine = new Engine()
start() { return this.#engine.start() } // delegate
}
Delegation lets you swap the held object, compose multiple collaborators, and avoid tight base-class coupling. It's the runtime sibling of prototypal delegation, done explicitly with object fields.
Traits are like mixins but with stricter conflict handling — they require explicit resolution when methods clash, rather than silently overwriting. JavaScript has no built-in traits; libraries emulate them.
// emulated trait: throw on conflict instead of silent override
function applyTrait(target, trait) {
for (const k of Object.keys(trait)) {
if (k in target) throw new Error(`conflict: ${k}`)
target[k] = trait[k]
}
}
The distinction is mostly academic in JS — both share behavior horizontally; the
difference is conflict policy. Plain Object.assign mixins are the common
reality.
Modern JS increasingly composes functions rather than objects — e.g. React hooks, higher-order functions, and middleware pipelines all build behavior by combining functions.
// composing custom hooks (function composition of behavior)
function useUser(id) {
const data = useFetch(`/users/${id}`)
const auth = useAuth()
return { ...data, isOwner: auth.id === id }
}
The trend in JS is away from class hierarchies toward composing small functions and objects. Understanding composition matters even if you rarely write classes.
Yes — if it's a class-factory mixin, it can use super.method() to call the
wrapped base class, enabling cooperative behavior.
const Loud = (Base) => class extends Base {
speak() { return super.speak().toUpperCase() } // augments base
}
class Animal { speak() { return 'hi' } }
class Dog extends Loud(Animal) {}
new Dog().speak() // 'HI'
Object-literal mixins can't use super (no base class), but class-factory mixins
sit in the prototype chain, so super works — letting mixins decorate inherited
methods.
Build an object by spreading capability objects into it — each capability is a small factory that closes over shared state.
const hasName = (name) => ({ getName: () => name })
const canGreet = (self) => ({ greet: () => `Hi, ${self.getName()}` })
function createPerson(name) {
const self = { ...hasName(name) }
return { ...self, ...canGreet(self) }
}
This factory-composition style avoids class/this/new entirely, assembling
behavior from functions. It's popular in functional-leaning JS codebases.
JavaScript has no interface keyword, but mixins act like implementable
interfaces of behavior — a class can "implement" several capabilities by mixing
them in, similar to implementing multiple interfaces.
class FileLogger extends Loggable(Serializable(Object)) {}
// "implements" Loggable and Serializable behaviors
Where a typed language declares implements A, B, JS mixes the actual behavior
in. (TypeScript adds real interface/implements on top for type checking.)
Deep hierarchies cause the fragile base class problem (a base change breaks far-off subclasses), tight coupling, rigidity (a type is stuck in one tree), and "yo-yo" debugging across many levels.
// hard to change Animal without risking Dog, Puppy, ServiceDog, ...
class ServiceDog extends Puppy extends Dog extends Animal {}
Flatten with composition: give objects the behaviors they need from small mixins/ collaborators instead of inheriting a long chain. Two or three levels is usually a practical ceiling.
Composed pieces are small, independent units you can test in isolation and swap with fakes. Inheritance ties behavior to a hierarchy, so testing a subclass drags in the whole base.
class Order {
constructor(payment) { this.payment = payment } // injected collaborator
checkout() { return this.payment.charge() }
}
new Order({ charge: () => 'mock' }).checkout() // easy to fake
Dependency injection + composition lets you substitute collaborators in tests. That testability is a major reason "composition over inheritance" holds.
Mixing into the prototype (Object.assign(Class.prototype, mixin)) shares one
copy across all instances. Mixing into each instance (in the constructor)
gives every object its own copy — more memory, but allows per-instance
customization.
Object.assign(User.prototype, mixin) // shared (efficient)
// vs
class User { constructor() { Object.assign(this, mixin) } } // per-instance
Prefer prototype mixing for shared stateless behavior. Per-instance mixing is for when each object needs independently modifiable methods/state.
A decorator wraps an object/function to add behavior while preserving the interface — composition by layering.
const withLogging = (fn) => (...args) => {
console.log('calling', fn.name, args)
return fn(...args)
}
const loggedFetch = withLogging(fetch)
Higher-order functions (and React HOCs) wrap to extend behavior without modifying the original — the same compositional idea as class-factory mixins, applied to functions/components.
Mixins can cause name collisions (silent overrides), make it unclear where a
method came from (implicit behavior), create hidden coupling between mixin and
host, and complicate this reasoning.
Object.assign(C.prototype, a, b, c) // which mixin defined `process`? unclear
Use them sparingly and namespace methods to reduce clashes. Often plain composition/delegation (an explicit collaborator object) is clearer than mixing methods into a prototype.
Compose exactly the behaviors required, rather than inheriting a broad base that includes extras.
const readable = (store) => ({ read: (k) => store[k] })
const writable = (store) => ({ write: (k, v) => { store[k] = v } })
const readOnly = { ...readable(data) } // only read
const readWrite = { ...readable(data), ...writable(data) }
This "interface segregation" via composition keeps objects minimal and intention- revealing — you grant precisely the capabilities needed, avoiding the bloat of a do-everything base class.
A mixin method's this is whatever object it's called on (the host instance), so
it operates on the host's data — that's what makes mixins reusable across types.
const describable = {
describe() { return `${this.type}: ${this.name}` }
}
Object.assign(Product.prototype, describable)
new Product().describe() // uses the Product's this.type / this.name
The mixin doesn't care which class it's in — it just uses this. Avoid arrow
functions in object-literal mixins (they'd capture the wrong this); use regular
method shorthand.
A practical rule of thumb:
- Inheritance — a true, stable is-a with one parent (
Circleis aShape). - Mixins — a capability shared by unrelated classes
(
Serializable,Comparable). - Composition/delegation — a has-a relationship or swappable collaborator
(
Carhas anEngine).
class Circle extends Shape {} // is-a
Object.assign(Circle.prototype, Serializable) // capability
class Car { #engine = new Engine() } // has-a
Default to composition; reach for inheritance only for genuine is-a hierarchies, and mixins for cross-cutting behaviors.
Practice tests are coming soon
Get notified when interactive mock interviews and quizzes launch.