Node.js is known for its efficient and scalable I/O model, which is based on a non-blocking, event-driven architecture. This means that I/O operations are handled asynchronously and do not block the execution of the main program. In contrast to traditional blocking I/O models, where the program waits for each I/O operation to complete before continuing to the next one, Node.js can execute multiple I/O operations simultaneously.

Here are some key points that explain how Node.js achieves non-blocking I/O:

Example

Blocking code example:

const getUserSync = (userId) => {
  const users = {
    1: { name: 'John', age: 35 },
    2: { name: 'Jane', age: 28 }
  };
  return users[userId];
}

const user = getUserSync(1);
console.log(user);

In this example, the getUserSync function returns a user object from a hardcoded list of users. This function is blocking, because it executes synchronously and returns the result immediately.

Non-blocking code example:

const getUserAsync = (userId, callback) => {
  const users = {
    1: { name: 'John', age: 35 },
    2: { name: 'Jane', age: 28 }
  };
  setTimeout(() => {
    callback(users[userId]);
  }, 1000);
}

getUserAsync(1, (user) => {
  console.log(user);
});

In this example, the getUserAsync function returns a user object from a hardcoded list of users, but it executes asynchronously, using the setTimeout function to delay the execution of the callback function by 1 second. The getUserAsync function takes a callback function as its second argument, which is called with the user object once it has been retrieved.