Skip to main content

Command Palette

Search for a command to run...

Writing cleaner JavaScript

Updated
•4 min read•View as Markdown

As developers, one of the best skills we can build is writing clean, maintainable, and scalable code. If you're learning JavaScript, chances are you've heard of the SOLID principles — a set of five best practices for writing clean, maintainable code.

SOLID is an acronym for five design principles:

  • S – Single Responsibility Principle

  • O – Open/Closed Principle

  • L – Liskov Substitution Principle

  • I – Interface Segregation Principle

  • D – Dependency Inversion Principle

These principles were originally introduced by Robert C. Martin (Uncle Bob) and are foundational to object-oriented design. They help reduce bugs, increase flexibility, and make your code easier to test and extend. While SOLID applies to many programming languages, it fits naturally into modern JavaScript, especially when building modular applications or working with front-end frameworks like React or Vue.

But instead of diving into all five at once, let’s start with just three of the most powerful ones: the Single Responsibility Principle (SRP), Liskov Substitution Principle (LSP), and Interface Segregation Principle (ISP). These three principles can drastically improve your code structure, especially in projects where you use classes or modules. Let’s break them down with JavaScript examples.

S – Single responsibility principle (SRP)

“A class or function should have one, and only one, reason to change.” In other words: Do one thing, and do it well.

When a class or function tries to do too much, it becomes harder to maintain and test. If you separate responsibilities into smaller units, your code becomes more readable and easier to change.

❌ Bad example:

class Order {
  calculateTotal() {
    // logic to calculate total
  }

  saveToDatabase() {
    // logic to save order
  }

  sendConfirmationEmail() {
    // logic to send email
  }
}

This class is handling three separate concerns: calculating totals, saving data, and sending emails.

✅ Good example:

class Order {
  calculateTotal() {
    // logic to calculate total
  }
}

class OrderRepository {
  save(order) {
    // save to database
  }
}

class EmailService {
  sendConfirmation(order) {
    // send email
  }
}

Now each class has one responsibility. Need to change your email logic? You only touch EmailService. Easy!

L – Liskov substitution principle (LSP)

“Subtypes must be substitutable for their base types without breaking the program.” In practice: If Bird is a base class, and Penguin extends Bird, you should be able to use Penguin anywhere you use Bird — without things breaking.

If a subclass violates the expectations set by its parent, your code becomes unpredictable.

❌ Bad Example:

class Bird {
  fly() {
    console.log("Flying");
  }
}

class Penguin extends Bird {
  fly() {
    throw new Error("Penguins can't fly!");
  }
}

function makeBirdFly(bird) {
  bird.fly();
}

const penguin = new Penguin();
makeBirdFly(penguin); // Error!

We expected a Bird to fly, but that doesn’t work for Penguin. This violates LSP.

✅ Good Example:

class Bird {
  move() {
    console.log("Moving");
  }
}

class FlyingBird extends Bird {
  move() {
    console.log("Flying");
  }
}

class Penguin extends Bird {
  move() {
    console.log("Waddling");
  }
}

function moveBird(bird) {
  bird.move();
}

moveBird(new FlyingBird()); // "Flying"
moveBird(new Penguin());    // "Waddling"

Now, all birds move, but each in their own way. Every subclass fits smoothly into the overall design.

Interface Segregation Principle (ISP)

"Clients should not be forced to depend on methods they do not use."

Split large interfaces into smaller, more specific ones.

❌ Bad example:

class Animal {
  eat() {}
  fly() {}
}

Now every animal must have a fly() method, even if it doesn't make sense (like for dogs).

✅ Good example:

class Eater {
  eat() {}
}

class Flyer {
  fly() {}
}

class Dog extends Eater {}
class Bird extends Eater {
  constructor() {
    super();
    this.flyer = new Flyer();
  }
}

Only apply what's needed to the appropriate classes.

Conclusion:

By applying just the S, L, and I of SOLID, we’re already on the path to writing smarter, more modular JavaScript:

SRP helps keep your code focused and clean.

LSP ensures your inheritance makes sense and doesn't break functionality.

ISP encourages us to design interfaces that are tailored to specific client needs, avoiding bloated or unnecessary code.

Together, these principles promote clarity, reduce technical debt, and make it easier to refactor or scale your codebase. Whether you're working solo or on a team, applying even a subset of SOLID improves long-term project health.

#references:

  1. GeeksforGeeks. "SOLID Principles in Programming: Understand With Real Life Examples." https://www.geeksforgeeks.org/solid-principle-in-programming-understand-with-real-life-examples/

  2. LogRocket Blog. "SOLID Principles for JavaScript." https://blog.logrocket.com/solid-principles-javascript/

  3. Wikipedia. "Single Responsibility Principle." https://en.wikipedia.org/wiki/Single-responsibility_principle

  4. Wikipedia. "Liskov Substitution Principle." https://en.wikipedia.org/wiki/Liskov_substitution_principle