Thursday 10 October 2024

NodeJS Interview Question and Answers

» What is Node and how it works?

  • Node is a open source and cross platform run time environment for executing JavaScript code outside of a browser.
  • So basically node server executes the JavaScript code not the browser.
  • Node run time uses V8 engine and C++ program to execute JavaScript.
  • How Node Works:
  • Node applications are asynchronous by default.
  • In node we have a single thread to handle all the requests. So when the requests arrives, that single thread is used to handle all those.

Example: "If we send a request to database, our thread doesn't have to wait for the database to return the data. While database is executing query same thread will be used to serve another request. When the database returns the result it puts a message in the Event Queue. Node continuously monitors this event queue in background and when it finds event in the queue it takes it process it".

» Is nodejs a programming language?

No, Node.js is not a programming language. Node.js is a runtime environment for executing JavaScript code on the server-side, and JavaScript is the programming language that is used to write code for Node.js.

JavaScript is a high-level programming language that is used to create dynamic web pages and web applications. It is primarily used in client-side development, running in web browsers to manipulate web page content and behavior. However, with Node.js, JavaScript can also be used for server-side development, allowing developers to write full-stack web applications using a single programming language.

Node.js provides a set of built-in modules that make it easier to work with files, network sockets, and other resources on the server-side. It also includes a package manager called npm (Node Package Manager) that makes it easy to install and manage third-party libraries and modules.

So, while Node.js is not a programming language itself, it is a powerful and flexible runtime environment that allows developers to use JavaScript on the server-side to create scalable and efficient web applications.

» What is server-side runtime environment?

A server-side runtime environment is a software environmt in which server-side applications run. This environment includes a set of libraries, frameworks, and tools that provide a runtime for executing server-side code. The server-side runtime environment is responsible for handling requests from clients and returning responses to clients.Examples of server-side runtime environments include:

Node.js: Node.js is a server-side runtime environment built on the V8 JavaScript engine. It provides an event-driven, non-blocking I/O model that makes it efficient for building scalable network applications.

Ruby on Rails: Ruby on Rails is a web application framework written in the Ruby programming language. It provides a runtime environment for server-side applications and includes features such as a routing system, database access, and templating.

Java EE: Java EE is a platform for building enterprise-level applications in Java. It provides a runtime environment for server-side applications and includes features such as servlets, JavaServer Pages (JSP), and Enterprise JavaBeans (EJB).

These server-side runtime environments provide a powerful and flexible platform for building scalable and efficient server-side applications.

» What is V8 JavaScript engine?

The V8 JavaScript engine is an open-source JavaScript engine developed by Google. It is written in C++ and is used to execute JavaScript code in the Google Chrome browser, as well as in other applications and tools. The V8 engine is designed to be fast and efficient, and it uses various techniques to optimize the performance of JavaScript code. For example, it compiles JavaScript code into machine code rather than interpreting it, which makes it faster to execute. It also uses just-in-time (JIT) compilation to dynamically optimize frequently executed code.

The V8 engine provides a number of features that make it a popular choice for developers, including:

High performance: The V8 engine is designed to execute JavaScript code quickly, making it a good choice for applications that require high performance.

Memory management: The V8 engine uses a garbage collector to manage memory, which reduces the risk of memory leaks and other memory-related issues.

Cross-platform support: The V8 engine can be used on a variety of platforms, including Windows, Mac, Linux, and Android.

Easy integration: The V8 engine is designed to be easy to integrate into other applications and tools, making it a popular choice for building custom JavaScript runtimes.

In addition to being used in the Google Chrome browser, the V8 engine is also used in other applications and tools, including the Node.js runtime, the Electron framework for building desktop applications, and the Deno runtime for building server-side applications in JavaScript and TypeScript.

» Is nodejs multithreaded?

Node.js is technically not multithreaded in the traditional sense, as it uses a single thread to execute JavaScript code. However, Node.js does support asynchronous programming techniques, such as callbacks, promises, and async/await, that allow developers to write code that can handle multiple I/O operations concurrently without blocking the execution of other tasks.

Node.js achieves this concurrency through its event-driven architecture and the use of the event loop. When an I/O operation is initiated, Node.js registers a callback function to handle the operation's completion. While the I/O operation is in progress, Node.js can continue to execute other code, and when the operation completes, the registered callback function is added to the event loop for execution.

This means that while Node.js is not truly multithreaded, it can still handle multiple I/O operations concurrently and provide scalable performance for server-side applications. In addition, Node.js can make use of multiple threads for certain operations, such as file I/O, by using worker threads or child processes to execute code in separate threads.

Node.js provides a cluster module that allows you to create child processes that can share the same port, allowing you to take advantage of multiple CPU cores for handling incoming requests. Each child process runs on a separate thread, allowing you to handle multiple requests concurrently.

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  console.log(`Master ${process.pid} is running`);

  // Fork workers
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`worker ${worker.process.pid} died`);
  });
} else {
  // Worker processes have access to the shared server
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('hello world\n');
  }).listen(8000);

  console.log(`Worker ${process.pid} started`);
}

In this example, the cluster.isMaster check is used to determine whether the current process is the master process or a worker process. If it is the master process, it forks the desired number of worker processes and listens for exit events.

Each worker process then creates an HTTP server and listens on port 8000 for incoming requests. Because each worker process runs on a separate thread, they can handle incoming requests concurrently, allowing for better performance and scalability.

Note that while the cluster module allows you to take advantage of multiple CPU cores, it does not provide true multithreading. Each worker process still uses a single thread to execute JavaScript code, but they can handle multiple requests concurrently using asynchronous I/O operations.

Worker Threads: Another way to take advantage of multiple threads in Node.js is to use the worker_threads module, which allows you to create true multithreaded applications in Node.js.

The worker_threads module provides a way to create separate Node.js threads that can communicate with each other using message passing. Each thread has its own event loop and can execute JavaScript code independently of the main thread.

Here's an example of using the worker_threads module to create a multithreaded application that calculates the sum of an array of numbers:

const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');

if (isMainThread) {
  // This is the main thread
  const numCPUs = require('os').cpus().length;
  const nums = Array.from({ length: 10000000 }, (_, i) => i + 1);
  const chunkSize = Math.ceil(nums.length / numCPUs);
  const workers = [];

  for (let i = 0; i < numCPUs; i++) {
    const worker = new Worker(__filename, {
      workerData: {
        start: i * chunkSize,
        end: (i + 1) * chunkSize,
        nums: nums.slice(i * chunkSize, (i + 1) * chunkSize)
      }
    });
    workers.push(worker);

    worker.on('message', (msg) => {
      console.log(`Worker ${worker.threadId}: Sum is ${msg}`);
    });

    worker.on('error', (err) => {
      console.error(`Worker ${worker.threadId} error: ${err}`);
    });

    worker.on('exit', (code) => {
      if (code !== 0) {
        console.error(`Worker ${worker.threadId} exited with code ${code}`);
      }
    });
  }

  for (let worker of workers) {
    worker.postMessage('calculate');
  }
} else {
  // This is a worker thread
  const sum = workerData.nums.reduce((acc, n) => acc + n, 0);
  parentPort.postMessage(sum);
}

In this example, the main thread creates an array of numbers and divides it into chunks based on the number of available CPU cores. It then creates a new worker thread for each CPU core and passes a slice of the array to each worker as workerData.

Each worker then calculates the sum of its slice of the array and sends the result back to the main thread using parentPort.postMessage(). The main thread listens for message events from each worker and logs the final result.

This approach allows you to take full advantage of multiple CPU cores in Node.js and can significantly improve the performance of CPU-bound tasks. However, it's important to note that creating too many worker threads can actually degrade performance, so it's important to strike a balance between using multiple threads and avoiding excessive overhead.

» How nodejs handles child threads?

Node.js is traditionally known for its single-threaded nature due to its use of the event-driven, non-blocking I/O model, but it also has a way to handle child threads in a more controlled manner through worker threads and child processes. Here’s how Node.js handles child threads:

Using Worker Threads: It introduced in nodejs v 10.5.0 and it allows you to run javascript code in parallel on different threads, which is useful for cpu bound tasks

Event loop and Worker threads share memory using SharedArrayBuffer

Workers are separate threads and can execute code independently

const { Worker, isMainThread, parentPort } = require('worker_threads');

if (isMainThread) {
  // This is the main thread
  const worker = new Worker(__filename);  // Create a worker thread
  worker.on('message', (msg) => console.log('Message from worker:', msg));
  worker.postMessage('Hello, worker!');
} else {
  // This is the worker thread
  parentPort.on('message', (msg) => {
    console.log('Message from main thread:', msg);
    parentPort.postMessage('Hello, main thread!');
  });
}

Using Child Processes: NodeJs also provides a way to create child processes using child_process module. These processes run independently and have their own memory space.

Child processes ar seperate OS PROCESSES AND NOT THREADS SO THEY DO NOT SHARE MEMORY WITH EVENT LOOP (MAIN PROCESS)

Child processes are suitablef or I/O boudn tasks that requires heavy computation or need to be handled outside of event loop

There are 3 main ways to create child processes:

exec: Runs a command in shell and buffers the output

spawn: Launches a new process with given command. It streams the output, making it more efficient for handling large data

fork:A special case of spawn is used specifically to spawn new nodejs processes and setup communication channels between processes

const { fork } = require('child_process');

// Forking a child process to execute another Node.js script
const child = fork('childScript.js');

// Sending a message to the child process
child.send({ hello: 'world' });

// Receiving a message from the child process
child.on('message', (msg) => {
  console.log('Message from child:', msg);
});

» What is REPL in nodejs?

REPL (READ-EVAL-PRINT LOOP) is interactive env which allows you to execute javascript code directly within terminal or cmd prompt.

Read: Reads input from user

Eval: It evaluates code

Print: It prints result of execution

Loop: It repeats this process, allowing you to continously enter and run more code

» What is a Buffer in NodeJS?

When working with things like files, images, videos or network streams you need to handle raw binary data (0s and 1s), the Buffer allows nodejs to work with think kind of data efficiently

Unlike strings, buffers can store raw binary data that JavaScript strings cannot present

» What is a module in NodeJS?

A module is a reusable block of code that encapsulates related functionality and can be loaded into other files using the require() function in NodeJS. Modules help to keep the code organized, modular, and easy to maintain.

» How to create a module in NodeJS?

To create a module in NodeJS, you need to export a function, object, or a variable from a file using the module.exports object. Here is an example:

// greet.js
module.exports = {
  greet: function(name) {
    console.log(`Hello, ${name}!`);
  }
};

You can then load this module into another file using the require() function:

// app.js
const greet = require('./greet');

greet('John'); // Output: Hello, John!

» Callback function in Node JS:

Callback is a function which gets called when the result of an asynchronous operation is ready.

Example:

// calling a getUser function
getUser(101, function(user){
 console.log('user id', user.id);  
  console.log('user name', user.username);  
});

function getUser(userId, callback){
 setTimeout(function(){
   callback({id:userId, username: "KJ"}); // callback function
  },2000);
}

» What is Callback Hell in Node JS:

  • Callback hell means nested callback functions
  • Callback hell a.k.a. Pyramid doom

Example:

getUser(param1, function(err, paramx){
    getUserDepartment(paramx, function(err, result){
        insertSomeData(result, function(err){
            doSomeAnotherOperation(someparameter, function(s){
                againDosomethingElse(function(x){
                });
            });
        });
    });
});

» What is difference between callbacks and promises:

  • Callbacks are simply functions in JavaScript which are to be called and then executed after the operation of an asynchronous function has finished.
  • In JavaScript functions itself considered as an Objects, so we all know that we can pass objects to another function as an argument, similarly we can pass functions as an argument to another functions.
  • setTimeout() is an example.
  • Promises are nothing but a much more improvised approach of handling asynchronous code as compare to callbacks.
  • Promises received two callbacks in constructor function: resolve and reject.
  • The resolve callback is used when the execution of promise performed successfully and the reject callback is used to handle the error cases.
  • The result of the computation might be that either the promise is fulfilled with value or rejected with a reason.

» What is difference between Promises and Observables

  • Promises:
  • Promises handles single event when asyc operation completes or fails.
  • If promises gets resolved then it returns a single value.
  • If promises gets rejected then it return an error.
  • Promises are not cancellable, means, if we initiate any promise request we cannot cancel it.
  • In Promises if request gets failed then we cannot retry it.
  • Promises are part of JavaScript.
  • Observable:
  • Observable is an array or sequence of event over time.
  • Observable works on streams of data.
  • Obsevable allows us to pass zero or more events where the callback is called for each event.
  • Unlike Promises obsevable are cancellable. We can do it by cancelling subscription.
  • If the request gets failed then we can retry it using retry() method.

» What is Mongoose in Node JS.

  • Mongoose in a JavaScript framework commonly used in Node.js applications.
  • It works a an ODM (Object Data Modeler) for Node applications.
  • Using mongoose we can easily implement validations and query API so that we can interact with MongoDB database.
  • It allows you to define objects with a strongly typed schema that is mapped to a MongoDB document.
  • Mongoose works with below schema types:
  • Number, String, Date, Buffer, Boolean, Mixed, Array, Object.
// Install mongoose
npm install mongoose --save

// Add mongoose package in your node.js file
var mongoose = require('mongoose');

// Connect with mongoose 
mongoose.connect('mongodb://localhost/dbname');

» Some important NodeJS packages for your application? (WIP)

const config = require('config'); // Manage the npm configuration files
const path = require('path'); // Path module provides utilities for working with file and directory paths
const logger = require('morgan'); // HTTP request logging moddleware. generates request logs
const cookieParser = require('cookie-parser'); // Extract cookies and puts cookie info in req obj
const bodyParser = require('body-parser'); // Extract body portion of request stream & exposes it on req.body
const bluebird = require('bluebird'); // Javascript promise library
const mongoose = require('mongoose'); // Object data modelling library for nodejs and mongodb

» What is middleware in ExpressJS?

Middleware is a function that sits between the client and the server and can intercept and modify the incoming request or the outgoing response. Middleware functions can be used for logging, authentication, error handling, and more.

» How to use middleware in ExpressJS?

const express = require('express');
const app = express();

// Middleware function
const logger = (req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next(); // Call the next middleware function
};

app.use(logger); // Use the middleware function

app.get('/', (req, res) => {
  res.send('Hello, World!');
});

app.listen(3000, () => {
  console.log('Server running at http://localhost:3000/');
});

» How to use middleware in ExpressJS?

To use middleware in ExpressJS, you can use the app.use method. Here is an example:

const express = require('express');
const app = express();

app.use((req, res, next) => {
  console.log(`Received request for ${req.url}`);
  next();
});

app.use(express.static('public'));

app.listen(3000, () => {
  console.log('Server started on port 3000');
});

This code defines two middleware functions: one that logs the URL of each request, and one that serves static files from the public directory. The app.use method is used to add these middleware functions to the Express application.

» How to handle form data in ExpressJS?

To handle form data in ExpressJS, you can use the body-parser middleware. Here is an example:

const express = require('express');
const bodyParser = require('body-parser');
const app = express();

// Use body-parser middleware
app.use(bodyParser.urlencoded({ extended: false }));

app.get('/', (req, res) => {
  res.send(`
    <form method="post" action="/submit">
      <input type="text" name="name" placeholder="Name">
      <button type="submit">Submit</button>
    </form>
  `);
});

app.post('/submit', (req, res) => {
  const name = req.body.name;
  res.send(`Hello, ${name}!`);
});

app.listen(3000, () => {
  console.log('Server running at http://localhost:3000/');
});

» How to use streams in NodeJS?

Streams are a powerful tool in NodeJS that allow you to process data piece by piece, rather than loading the entire data into memory at once. There are four types of streams in NodeJS: Readable, Writable, Duplex, and Transform. Here is an example of using a Readable stream to read a file:

const fs = require('fs');

const readStream = fs.createReadStream('myfile.txt');

readStream.on('data', (chunk) => {
  console.log(`Received ${chunk.length} bytes of data.`);
});

readStream.on('end', () => {
  console.log('Finished reading file.');
});

» Explain HTTP status codes

HTTP status codes indicate whether a request has been successfully completed. They are grouped into five ranges, each of which defines where the error occurred and the number of which defines the error itself:

1XX: Informational codes, which indicate that the server is acknowledging and processing the request

2XX: Success codes, which indicate that the server has successfully received, understood, and processed the request

3XX: Redirection codes, which indicate that the server has received the request but there is a redirect to somewhere else

4XX: Client error codes, which indicate that the server could not find or reach the page or website

5XX: Server error codes, which indicate that the client made a valid request but the server was unable to complete it

» What are the HTTP codes to describe an attempt to access retricted resources

401 Unauthorized: The request requires user authentication. This status is sent when authentication is possible but has failed or has not yet been provided.

403 Forbidden: The server understands the request but refuses to authorize it. This is used when the server knows the identity of the client but does not have permission to access the resource.

» setImmediate() vs setTimeout() vs process.nextTick()

setTimeout(): is used to schedule a function to be executed after a specified period of time in milliseconds.

setImmediate(): is used to schedule a function to be executed after the current event loop iteration completes. It has lower priority that I/O (input output) events but higher priority than setTimeout()

process.nextTick(): is used to schedule a function to be executed immediately after current function completes. It ensures that a callback function is executed before any I/O events in the event loop.

process.nextTick() vs setImmediate(): process.nextTick() actually fires more immediately than setImmediate() within same event loop phase.

When to use setImmediate() : When you want to execute code after the current event loop cycle, allowing I/O events and other callbacks to be processed first. Useful for breaking up long-running operations to prevent blocking the event loop.

console.log('Start');

setImmediate(() => {
  console.log('setImmediate callback');
});

console.log('End');
Start
End
setImmediate callback

When to use processNextTick() : When you need to execute code as soon as possible, but after the current function has finished executing. Useful for deferring callbacks or preventing stack overflows in recursive functions..

console.log('Start');

setImmediate(() => {
  console.log('setImmediate callback');
});

console.log('End');

//output

Start
End
setImmediate callback

» How does Node.js handle memory management and garbage collection?

Node.js handles memory management and garbage collection through the V8 JavaScript engine, which is the same engine used by Google Chrome. V8 provides automatic memory management features, including garbage collection, which helps developers by abstracting the low-level memory allocation and deallocation processes.

Memory Management in Node.js:

Memory Allocation:

JavaScript Objects and Data Structures: When you create variables, objects, or data structures in your Node.js application, V8 allocates memory for them on the heap.

Native Resources: Node.js applications often interact with native resources like file handles or network connections, which are managed outside of the V8 heap.

You can monitor your application's memory usage using: process.memoryUsage(): Provides details about memory usage. Profiling tools like Chrome DevTools, Node.js Heap Snapshots, or third-party monitoring services.

Garbage Collection in Node.js

V8 Garbage Collector

Generational Garbage Collection: V8 uses a generational garbage collector that divides the heap into two main spaces:

New Space (Young Generation): Stores short-lived objects.

Old Space (Old Generation): Stores long-lived objects that have survived garbage collection cycles.

Garbage Collection Phases:

Scavenge (Minor GC): Quickly collects garbage from the New Space.

Mark-Sweep and Mark-Compact (Major GC): More thorough collection in the Old Space, which can be time-consuming and may impact performance.

The issue seems to be with the formatting or display of the code. Let’s fix that by ensuring the code is clearly visible with proper spacing and margins. Here’s an improved way to display the code: ---

» What are the main differences between CommonJS and ES6 modules in Node.js?

In Node.js, modules can be managed using two systems: CommonJS and ES6 modules (also known as ECMAScript modules or ESM). Below are the key differences with simple examples to illustrate each point.

1. Syntax Differences

CommonJS uses require() to import modules and module.exports or exports to export them.

const greet = require('./moduleA');

greet(); // Output: Hello from CommonJS module

ES6 Modules use import and export statements.

import { greet } from './moduleB.js';

greet(); // Output: Hello from ES6 module

2. Module Loading

CommonJS modules are loaded synchronously, meaning the module is loaded and executed before the rest of the code continues. This is acceptable in server-side applications where blocking is less of a concern.

ES6 Modules can be loaded asynchronously, allowing for non-blocking execution and better performance in applications where it's critical.

3. Export Types

With CommonJS, you typically export a single object that contains all exports.

module.exports = {
  name: 'CommonJS Module',
  greet() {
    console.log('Hello from ' + this.name);
  }
};

In ES6 Modules, you can have multiple named exports and a single default export.

export const name = 'ES6 Module';

export function greet() {
  console.log('Hello from ' + name);
}

4. Usage in Node.js

CommonJS is the default module system in Node.js. Most Node.js applications and packages use CommonJS.

ES6 Modules are supported in Node.js versions 14.x and above. To use them, you need to either use the .mjs file extension or set "type": "module" in your package.json.

{
  "name": "my-module",
  "version": "1.0.0",
  "type": "module"
}

5. Static vs. Dynamic Imports

CommonJS allows dynamic require() calls anywhere in the code, which can hinder optimization.

ES6 Modules use static import statements at the top of the file, enabling better optimization and tree-shaking.

6. Interoperability

Mixing module types can be tricky:

  • When importing a CommonJS module into an ES6 module, you might need to access the default property.
  • Importing ES6 modules into CommonJS modules may require dynamic import() or other workarounds.

Summary

CommonJS uses require() and module.exports, is synchronous, and is the traditional module system in Node.js.

ES6 Modules use import and export, support asynchronous loading, and align with the modern JavaScript standard.

For new projects, especially those that require modern features and better optimization, it's recommended to use ES6 modules.

» How do you handle real-time communication in Node.js, and what are the differences between WebSockets and Server-Sent Events (SSE)?

Handling real-time communication in Node.js is commonly achieved using technologies like WebSockets or Server-Sent Events (SSE). Both are used to send updates from the server to the client, but they serve different purposes and have distinct behaviors.

Below, we explore how real-time communication works with Node.js and the key differences between WebSockets and SSE.

---

1. WebSockets

WebSockets provide a two-way, full-duplex communication channel between the client and the server. This means both the client and the server can send messages to each other in real time.

const WebSocket = require('ws');

const server = new WebSocket.Server({ port: 8080 });

server.on('connection', socket => {
  console.log('Client connected');

  socket.on('message', message => {
    console.log(`Received: ${message}`);
    socket.send('Hello from server!');
  });
});

How it works: A WebSocket connection is established using a handshake. Once connected, the client and server can both send and receive messages at any time.

---

2. Server-Sent Events (SSE)

Server-Sent Events (SSE) allow the server to push updates to the client over an HTTP connection. SSE is a one-way communication channel where the server can send messages to the client, but the client cannot send messages back.

const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache'
  });

  setInterval(() => {
    res.write('data: Hello from server\n\n');
  }, 1000);
});

server.listen(8080, () => {
  console.log('SSE server running on port 8080');
});

How it works: With SSE, the server sends events over the connection whenever it has new data. The client maintains an open connection to receive the updates.

---

3. Key Differences between WebSockets and SSE

Feature WebSockets Server-Sent Events (SSE)
Communication Type Full-duplex (two-way) Half-duplex (server to client only)
Use Case Chat apps, multiplayer games Stock prices, news updates
Connection Type Custom protocol over TCP HTTP-based
Reconnection Requires custom logic Automatic reconnection
Browser Support Widely supported Limited in older browsers
---

4. When to Use WebSockets vs. SSE

  • Use WebSockets when you need two-way communication, such as in chat applications, multiplayer games, or collaborative tools.
  • Use SSE when you only need the server to push updates, like in stock price feeds, live news updates, or notifications.

Summary

Both WebSockets and SSE are useful for real-time communication in Node.js, but they serve different purposes. WebSockets provide bi-directional communication, making them ideal for scenarios like chat apps. SSE offers a simpler, one-way communication channel suitable for continuous data streams like notifications or real-time dashboards.

No comments:

Post a Comment