Node - Js Interview Questions
Node - Js Interview Questions
When functions can be treated like any other variable then those functions are first-
class functions. There are many other programming languages, for example, scala,
Haskell, etc which follow this including JS. Now because of this function can be
passed as a param to another function(callback) or a function can return another
function(higher-order function). map() and filter() are higher-order functions that are
popularly used.
Node.js is a virtual machine that uses JavaScript as its scripting language and runs
Chrome’s V8 JavaScript engine. Basically, Node.js is based on an event-driven
architecture where I/O runs asynchronously making it lightweight and efficient. It is
being used in developing desktop applications as well with a popular framework
called electron as it provides API to access OS-level features such as file system,
network, etc.
5. Explain the steps how “Control Flow” controls the functions calls?
Control the order of execution
Collect data
Limit concurrency
Call the following step in the program.
The main advantage of using promise is you get an object to decide the action that
needs to be taken after the async task completes. This gives more manageable code
and avoids callback hell.
A fork in general is used to spawn child processes. In node it is used to create a new
instance of v8 engine to run multiple workers to execute the code.
Node.js was created explicitly as an experiment in async processing. This was to try a
new theory of doing async processing on a single thread over the existing thread-
based implementation of scaling via different frameworks.
10. How do you create a simple server in Node.js that returns Hello
World?
var http = require("http");
http.createServer(function (request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('Hello World\n');
}).listen(3000);
13. List down the two arguments that async.queue takes as input?
Task Function
Concurrency Value
For example, you have a file for all utils functions with util to get solutions in a
different programming language of a problem statement.
Thus using module.exports we can use these functions in some other file:
ESLint can be used with any IDE to ensure a consistent coding style which further
helps in maintaining the codebase.
For the above example, we are passing callback functions and it makes the code
unreadable and not maintainable, thus we should change the async logic to avoid
this.
Whatever that is async is managed by event-loop using a queue and listener. We
can get the idea using the following diagram:
So when an async function needs to be executed(or I/O) the main thread sends it to
a different thread allowing v8 to keep executing the main code. Event loop involves
different phases with specific tasks such as timers, pending callbacks, idle or prepare,
poll, check, close callbacks with different FIFO queues. Also in between iterations it
checks for async I/O or timers and shuts down cleanly if there aren't any.
The main loop is single-threaded and all async calls are managed by libuv library.
For example:
const crypto = require("crypto");
const start = Date.now();
function logHashTime() {
crypto.pbkdf2("a", "b", 100000, 512, "sha512", () => {
console.log("Hash: ", Date.now() - start);
});
}
logHashTime();
logHashTime();
logHashTime();
logHashTime();
Hash: 1213
Hash: 1225
Hash: 1212
Hash: 1222
This is because libuv sets up a thread pool to handle such concurrency. How many
threads will be there in the thread pool depends upon the number of cores but you
can override this.
process.nextTick() sets the callback to execute but setImmediate pushes the callback
in the queue to be executed. So the event loop runs in the following manner
timers–>pending callbacks–>idle,prepare–>connections(poll,data,etc)–>check–
>close callbacks
In this process.nextTick() method adds the callback function to the start of the next
event queue and setImmediate() method to place the function in the check phase of
the next event queue.
Since the node has an event loop that can be used to handle all the I/O operations in
an asynchronous manner without blocking the main function.
So for example, if some network call needs to happen it will be scheduled in the
event loop instead of the main thread(single thread). And if there are multiple such
I/O calls each one will be queued accordingly to be executed separately(other than
the main thread).
Thus even though we have single-threaded JS, I/O ops are handled in a nonblocking
way.
Streams are instances of EventEmitter which can be used to work with streaming data
in Node.js. They can be used for handling and manipulating streaming large
files(videos, mp3, etc) over the network. They use buffers as their temporary storage.
Middleware comes in between your request and business logic. It is mainly used to
capture logs and enable rate limit, routing, authentication, basically whatever that is
not a part of business logic. There are third-party middleware also such as body-
parser and you can write your own middleware for a specific use case.
Reactor pattern again a pattern for nonblocking I/O operations. But in general, this is
used in any event-driven architecture.
The server is responsible for initializing the routes, middleware, and other application
logic whereas the app has all the business logic which will be served by the routes
initiated by the server. This ensures that the business logic is encapsulated and
decoupled from the application logic which makes the project more readable and
maintainable.
Exit codes give us an idea of how a process got terminated/the reason behind
termination.
A few of them are:
Uncaught fatal exception - (code - 1) - There has been an exception that is not
handled
Unused - (code - 2) - This is reserved by bash
Fatal Error - (code - 5) - There has been an error in V8 with stderr output of
the description
Internal Exception handler Run-time failure - (code - 7) - There has been an
exception when bootstrapping function was called
Internal JavaScript Evaluation Failure - (code - 4) - There has been an
exception when the bootstrapping process failed to return function value when
evaluated.
Stubs are used in writing tests which are an important part of development. It
replaces the whole function which is getting tested.
External calls which make tests slow and difficult to write (e.g HTTP calls/ DB
calls)
Triggering different outcomes for a piece of code (e.g. what happens if an
error is thrown/ if it passes)
EventEmitter is a Node.js class that includes all the objects that are basically capable
of emitting events. This can be done by attaching named events that are emitted by
the object using an eventEmitter.on() function. Thus whenever this object throws an
even the attached functions are invoked synchronously.
Node.js applications run on a single processor, which means that by default they
don’t take advantage of a multiple-core system. Cluster mode is used to start up
multiple node.js processes thereby having multiple instances of the event loop.
When we start using cluster in a nodejs app behind the scene multiple node.js
processes are created but there is also a parent process called the cluster
manager which is responsible for monitoring the health of the individual instances
of our application.
Clustering in Node.js
The Thread pool is handled by the libuv library. libuv is a multi-platform C library that
provides support for asynchronous I/O-based operations such as file systems,
networking, and concurrency.
Thread Pool
Cluster:
Worker threads:
Performance API provides us with tools to figure out the necessary performance
metrics. A simple example would be using async_hooks and perf_hooks
'use strict';
const async_hooks = require('async_hooks');
const {
performance,
PerformanceObserver
} = require('perf_hooks');
const set = new Set();
const hook = async_hooks.createHook({
init(id, type) {
if (type === 'Timeout') {
performance.mark(`Timeout-${id}-Init`);
set.add(id);
}
},
destroy(id) {
if (set.has(id)) {
set.delete(id);
performance.mark(`Timeout-${id}-Destroy`);
performance.measure(`Timeout-${id}`,
`Timeout-${id}-Init`,
`Timeout-${id}-Destroy`);
}
}
});
hook.enable();
const obs = new PerformanceObserver((list, observer) => {
console.log(list.getEntries()[0]);
performance.clearMarks();
observer.disconnect();
});
obs.observe({ entryTypes: ['measure'], buffered: true });
setTimeout(() => {}, 1000);
This would give us the exact time it took to execute the callback.
Performance API provides us with tools to figure out the necessary performance
metrics.