This question was asked in Uber's frontend interview and Swiggy's frontend interview.

Create a simple Observable class that implements the observer pattern. The class should:

Example

const observable = new Observable();

// Subscribe to changes
const subscription = observable.subscribe(data => {
  console.log('Received:', data);
});

// Notify subscribers
observable.notify('Hello!'); // logs: "Received: Hello!"

// Unsubscribe
subscription.unsubscribe();

// No longer logs anything
observable.notify('Hello again!');

It is the variation of the pub-sub - 1 and implements the observer design pattern.

If you follow the example, we have to create a new instance of Observable which will return two methods subscribe(callback) and notify(msg)

subscribe(callback) returns another method unsubscribe() that removes the current callback.

When the notify(msg) is invoked, the message is passed to the callback function that can accept it as argument and then render it.

function Observable() {
  this.observers = [];

  this.subscribe = function(callback) {
    if (typeof callback !== 'function') {
      throw new Error('Callback must be a function');
    }

    // Convert to object for easy access
    const observer = { callback: callback };
    this.observers.push(observer);

    // Return subscription object with unsubscribe method using arrow function
    return {
      unsubscribe: () => {
        const index = this.observers.indexOf(observer);
        if (index > -1) {
          this.observers.splice(index, 1);
        }
      }
    };
  };

  // Can accept any number of arguments
  this.notify = function(...data) {
    this.observers.forEach(function(observer) {
      try {
        // Pass the arguments to callback
        observer.callback(...data);
      } catch (error) {
        console.error('Error in observer callback:', error);
      }
    });
  };

  // Additional utility methods
  this.hasObservers = function() {
    return this.observers.length > 0;
  };

  this.getObserverCount = function() {
    return this.observers.length;
  };

  this.clear = function() {
    this.observers = [];
  };
}

Testcase

const observable = new Observable();

// Multiple subscribers
const sub1 = observable.subscribe(function(data) {
  console.log('Sub1:', data);
});

const sub2 = observable.subscribe(function(data) {
  console.log('Sub2:', data);
});

const sub3 = observable.subscribe(function(data) {
  console.log('Sub3:', data);
});

console.log('Observer count:', observable.getObserverCount()); // 3

observable.notify('Broadcast message');
// Logs:
// Sub1: Broadcast message
// Sub2: Broadcast message
// Sub3: Broadcast message

// Unsubscribe one
sub2.unsubscribe();

observable.notify('Another message');
// Logs:
// Sub1: Another message
// Sub3: Another message

// Clear all observers
observable.clear();
observable.notify('No one listening'); // Nothing logged

Explanation