The Bridge Design Pattern in JavaScript

Christopher T.

April 25th, 2022

Share This Post

In this article we will be going over the Bridge Design Pattern in JavaScript. This is one of the top used patterns that make a significant impact in software applications. It is a pattern that easily promotes a separation of concerns in its implementation and it's scalable.

Here is diagram depicting this pattern:


There are usually two main participants (or entity, whichever you want to call it) that are involved in the Bridge Pattern.

The first and top most part is the abstract layer. This can be implemented simply as a class:

class Person {
  constructor(name) { = name

  talk(message) {

In the Bridge Pattern, the abstract layer declares the base interface methods and/or properties. However, they do not care about the implementation details because that isn't their job. To be able to reap the advantages of this pattern it must be kept this way so that our code later does not become tightly coupled and remains manageable.

The abstract layer instead opens bridges which then leads to the second main part of the pattern: the implementation layers (which are often implemented as classes in practice) are attached to these bridges, which the client (or you) call the shots. The word "attached" is my form of a human readable term to understand the code term which are references or pointers:


The "bridge" can visibly appear in code like this:

class Theme {
  constructor(colorScheme) {
    this.colorScheme = colorScheme // Bridge declared

  getColorScheme() {
    return this.colorScheme // Bridge reference/pointer

If you've visited websites like or they have a theme feature that you can access inside your profile. There is usually a toggle theme button. The theme is the abstract layer. The actual implementation in toggling between light and dark are most likely located outside of the abstract layer location within the implementation layer(s).

Where and when should the Bridge Pattern be used?

Some implementations in the real world are coded in a way where the "bridge effect" goes "live" during run time. When you need this type of coupling / binding between two objects this is when you can use the Bridge Pattern to your advantage.

A good example of this is twilio-video, a JavaScript library that lets you add real time voice and video to your web applications (like Zoom). In this library, The Room always instantiates as an empty room. The class keeps a pointer to a LocalParticipant, (when you join a video chat room you are the LocalParticipant on your screen) but the LocalParticipant doesn't actually run or become instantiated yet until it connects and is finished subscribing to the room which is only possible in running code.

If you scan through their code you will spot bridges in a lot of areas. A video chat session cannot be created without a Room, and a room does not start until there are at least two Participants. But a Participant cannot begin streaming until they start their local audio/video MediaTracks. These classes work together in a top down hierarchy. When you start to have multiple classes that are coupled together this is also a good time to consider the Bridge Pattern.

Another scenario where the Bridge Pattern is useful is when you want to share an implementation of some object with multiple objects.

For example, the MediaStreamTrack class represents a media track for a stream. The two most common implementations that "bridge" from it are audio and video tracks.


In addition, the implementation details are usually hidden within the derived classes.


Let's implement our own variation of the Bridge Pattern to get a good feel of a problem and solution it brings to the table.

Let's start with a generic Thing class which can represent any thing:

class Thing {
  constructor(name, thing) { = name
    this.thing = thing

We can create a high level abstraction class that extends Thing. We can call this LivingThing and will define a method called eat. All living things in the real world are born with the ability to eat in order to stay alive. We can mimic this in our code. This will stay in the high level abstract layer:

class LivingThing extends Thing {
  constructor(name, bodyParts) {
    super(name, this) = name
    // Bridge
    this.mouth = bodyParts?.mouth || null

  eat(food) {
    return this

We can see that we opened a bridge to the Mouth class. Let's define that class next:

class Mouth extends Thing {
  constructor() {
    super('mouth', this)

  chew() {}
  open() {}
  swallow() {}

The thing (no pun intended) to consider now is that our Mouth will be an implementation layer where we write the logic for communicating between the mouth and food.

This implementation is entirely based in Mouth. The LivingThing does not care about these implementation details and instead delegates this role entirely to its implementation classes which in our case is Mouth.

Let's pause and talk about this part for a moment. If LivingThing is not involved in any of its implementations this is actually a useful concept to us. If we can make other LivingThings that only need to provide the interface for implementations to derive from, then we can make a wider range of classes for other scenarios.

In an MMORPG game we can use the LivingThing and make more of them where they all inherit a pointer to a mouth automatically:

class Character extends LivingThing {
  constructor(name, thing) {
    super(name, this)
    this.thing = thing
    this.hp = 100
    this.chewing = null

  attack(target) {
    target.hp -= 5
    return this

  chew(food) {
    this.chewing = food
    return this

  eat(food) {
    this.hp += this.chewing.hpCount
    return this

class Swordsman extends Character {}
class Rogue extends Character {}
class Archer extends Character {}
class Sorceress extends Character {}

class Potion {
  constructor(potion) {
    this.potion = potion

  consume(target) {
    if (this.potion) {
      this.potion = null

class Food {...}

const sally = new Sorceress()
const mike = new Rogue()

mike.attack(sally) Food(...))

The bridge pattern is well known to enable developers to build cross-platform applications. We can already see this capability in our examples. We can build this same MMORPG game by reusing LivingThing on a new code base. We only need to re-implement the implementation layers like Mouth in order to create bindings to different platforms.

We aren't limited to games. Since our LivingThing is generic and makes sense for anything that moves it's possible we can use it to create something entirely different like a robot as an IoT device program and simulate eating behavior with LivingThing.

Going back into our pretend MMORPG game, bridges can be used to create more bridges. MMORPG usually have some profile page where users can edit their settings.

This Profile can itself utilize the Bridge Design Pattern to define a suite of pieces to make it function like a profile api:

let key = 0

class Profile {
  constructor({ avatar, character, gender, username }) {
    this.character = null // Bridge
    this.gender = null
    this.username = username = ++key

  setCharacter(value) {
    this.character = value
    return this

  setGender(value) {
    this.gender = value
    if (value === 'female') {
    } else {
    return this

  setUsername(value) {
    this.username = value
    return this

  showRecommendedEquipments() {
    // Do something with this.character

  save() {
    return fetch(`${key}`, {
      method: 'POST',
      body: JSON.stringify({
        character: this.character,
        gender: this.gender,
        username: this.username,

If you've read some of my other articles this might feel similar to the Adapter or Strategy pattern.

There are distinct differences that solve different problems however:

In the Adapter pattern the problem it solves starts from the code (or prior to runtime) where we would construct the Adapter first then immediately start with the rest:


function adapter() {
  return function (config) {
    var mockAdapter = this
    // axios >= 0.13.0 only passes the config and expects a promise to be
    // returned. axios < 0.13.0 passes (config, resolve, reject).
    if (arguments.length === 3) {
      handleRequest(mockAdapter, arguments[0], arguments[1], arguments[2])
    } else {
      return new Promise(function (resolve, reject) {
        handleRequest(mockAdapter, resolve, reject, config)

Compare that with our earlier snippets of twilio-video and you will feel the difference immediately.


And that concludes the end of this post! I hope you found this to be valuable and look out for more in the future!

Top online courses in Web Development


Read every story from jsmanifest (and thousands of other writers on medium)

Your membership fee directly supports jsmanifest and other writers you read. You'll also get full access to every story on Medium.

Subscribe to the Newsletter

Get continuous updates


© jsmanifest 2023