FrontendPrep
Menu
Topics
Questions
Guides
Challenges
Soon
Back to JavaScript Questions
javascriptHard

Programming Paradigms in JavaScript: Functional vs. Object-Oriented

Understand JavaScript's multi-paradigm nature. Compare Functional Programming (pure functions, immutability, composition) with Object-Oriented Programming (classes, encapsulation, inheritance).

Programming Paradigms in JavaScript: Functional vs. Object-Oriented

One of JavaScript's greatest strengths is that it is a multi-paradigm language. It does not force you into a single design box. Instead, it supports Procedural (Imperative), Object-Oriented (OOP), and Functional Programming (FP).

In technical interviews and system design discussions, senior engineers are expected to justify their architectural patterns. Knowing the mechanics, benefits, and tradeoffs of Functional vs. Object-Oriented code in JavaScript is key to structuring clean, scalable web apps.


1. Declarative vs. Imperative Programming

Before comparing OOP and FP, it is helpful to understand the difference between Imperative and Declarative code:

  • Imperative Programming (How): You write line-by-line instructions telling the engine exactly how to do something, managing states and index pointers manually (e.g., standard for loops).
  • Declarative Programming (What): You write code describing what you want to achieve, abstracting away the control flow (e.g., using map or filter).
const numbers = [1, 2, 3, 4];
 
// Imperative (Procedural / Loop state management)
let doubledImperative = [];
for (let i = 0; i < numbers.length; i++) {
  doubledImperative.push(numbers[i] * 2);
}
 
// Declarative (Functional abstraction)
const doubledDeclarative = numbers.map(x => x * 2);

Functional programming is highly declarative, whereas classical OOP can mix both styles.


2. Object-Oriented Programming (OOP) in JavaScript

Object-Oriented Programming organizes software design around objects (data structures containing properties and methods) rather than logic and functions.

Core Pillars of OOP

  1. Encapsulation: Grouping related data and behaviors inside an object while hiding internal state.
  2. Abstraction: Hiding complex implementation details and showing only essential features (e.g. interfaces, private variables).
  3. Inheritance: Passing properties and methods from a parent object/class to a child object. In JavaScript, this is achieved via the Prototype Chain.
  4. Polymorphism: The ability for different objects to respond to the same method call in unique ways (e.g., overriding a parent method).
class PaymentProcessor {
  #apiKeys; // Private field (Encapsulation/Abstraction)
  
  constructor(keys) {
    this.#apiKeys = keys;
  }
  
  processPayment(amount) {
    throw new Error("processPayment() must be implemented!"); // Interface contract
  }
}
 
// Inheritance & Polymorphism
class StripeProcessor extends PaymentProcessor {
  processPayment(amount) {
    console.log(`Processing $${amount} via Stripe.`);
  }
}

OOP Tradeoffs in JavaScript

  • Pros: Natural for modeling real-world domain structures; promotes code reuse through inheritance; easy to map to relational databases.
  • Cons: Can lead to deep, brittle inheritance hierarchies ("fragile base class" problem); objects are mutable by default, which can lead to side-effects and concurrency bugs.

3. Functional Programming (FP) in JavaScript

Functional Programming is a paradigm that treats computation as the evaluation of mathematical functions, avoiding state changes and mutable data.

Core Concepts of FP

  1. First-Class & Higher-Order Functions: JavaScript treats functions as values. You can assign them to variables, pass them as arguments (callbacks), and return them from other functions.
  2. Pure Functions: A function is pure if:
    • It always returns the same output for the same input.
    • It produces no side-effects (it does not modify global variables, mutate input objects, write to files, or execute network calls).
  3. Immutability: Data cannot be modified after it is created. Instead of updating an object's properties, you create a new shallow copy of the object containing the updates.
  4. Composition: Combining simple functions to build complex transformations (e.g., piping outputs).
// Pure function + Immutability
const updateUserRole = (user, newRole) => {
  return { ...user, role: newRole }; // Returns a new object reference
};
 
const user = { name: "Alice", role: "guest" };
const adminUser = updateUserRole(user, "admin");
 
console.log(user.role);      // "guest" (Original data remains immutable)
console.log(adminUser.role); // "admin"

FP Tradeoffs in JavaScript

  • Pros: Highly predictable and testable (pure functions are easy to unit test because they have no mock dependency requirements); concurrency-safe because there is no shared mutable state; leads to modular, composable designs.
  • Cons: High memory overhead (creating new objects/arrays instead of mutating them causes garbage collection overhead); steep learning curve; less intuitive when modeling physical systems.

4. Side-by-Side Comparison: Modeling an Invoice Processor

Let's look at how OOP and FP tackle the same requirement: processing a list of items, applying tax, filtering out free items, and calculating the final total invoice sum.

The OOP Approach

The OOP design wraps the state inside an instance object and modifies it iteratively.

class Invoice {
  constructor(items) {
    this.items = items;
    this.total = 0;
  }
 
  // Mutates internal state (total)
  processInvoice(taxRate) {
    this.total = 0;
    for (let i = 0; i < this.items.length; i++) {
      const item = this.items[i];
      if (item.price > 0) { // filter free
        const taxedPrice = item.price * (1 + taxRate);
        this.total += taxedPrice;
      }
    }
  }
 
  getTotal() {
    return this.total;
  }
}
 
const invoice = new Invoice([{ name: "Book", price: 20 }, { name: "Pen", price: 0 }]);
invoice.processInvoice(0.1);
console.log(invoice.getTotal()); // 22

The FP Approach

The FP design separates the operations into tiny, composable pure functions and processes the data immutably.

const items = [{ name: "Book", price: 20 }, { name: "Pen", price: 0 }];
 
// Pure modular functions
const isNotFree = (item) => item.price > 0;
const applyTax = (taxRate) => (item) => ({ ...item, price: item.price * (1 + taxRate) });
const sumPrices = (acc, item) => acc + item.price;
 
// Composable data pipeline
const calculateInvoiceTotal = (itemsList, taxRate) => {
  return itemsList
    .filter(isNotFree)
    .map(applyTax(taxRate))
    .reduce(sumPrices, 0);
};
 
const total = calculateInvoiceTotal(items, 0.1);
console.log(total); // 22

5. Composition vs. Inheritance

A common senior-level software architecture topic is Composition over Inheritance.

  • Inheritance (OOP): Design by defining what class an object is (e.g. Dog is an Animal). This creates rigid hierarchical lines.
  • Composition (FP/Modern OOP): Design by defining what an object can do (combining behaviors together).

If you have subclasses like Robot and Dog, and you want to build a RoboDog, inheritance forces you into complex base class splits. Composition merges behavior objects cleanly:

const canBark = (state) => ({
  bark: () => console.log(`${state.name} says Woof!`)
});
 
const canDrive = (state) => ({
  drive: () => console.log(`${state.name} is moving on wheels.`)
});
 
// Composed function factory
const createRoboDog = (name) => {
  const state = { name };
  return {
    ...state,
    ...canBark(state),
    ...canDrive(state)
  };
};
 
const sparky = createRoboDog("Sparky");
sparky.bark();  // "Sparky says Woof!"
sparky.drive(); // "Sparky is moving on wheels."

6. Key Takeaways

  • Multi-Paradigm: JavaScript supports both FP and OOP, allowing you to choose the best pattern for the specific task.
  • OOP encapsulates state and methods inside mutable objects, inheriting behavior via prototype delegation.
  • FP relies on pure functions, data immutability, and function composition to create highly testable and predictable pipelines.
  • Prefer Composition over Inheritance to avoid rigid class models and fragile structures.
  • Modern React patterns (like Hooks and functional components) lean heavily Functional, whereas utility libraries and backend Node systems frequently combine both styles.

Share this Resource

Help other developers level up by sharing this study guide.

More Technical Questions

Expand your mastery. Deep dive into other frontend interview challenges in this category.