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 Inheritance

A 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 (Circle is a Shape).
  • Mixins — a capability shared by unrelated classes (Serializable, Comparable).
  • Composition/delegation — a has-a relationship or swappable collaborator (Car has an Engine).
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.