JavaScript Developer Interview Questions For 5 Years Of Experience

Design patterns in JavaScript are reusable solutions to common software design problems. They provide guidelines and best practices for structuring and organizing code, improving code quality, maintainability, and reusability. Here are some commonly used design patterns in JavaScript: 1. Module Pattern: Encapsulates related functions and variables into a single module, providing a way to create private and public members. It promotes encapsulation and helps avoid global namespace pollution.

var CounterModule = (function() {
  var count = 0;

  return {
    increment: function() {
    getCount: function() {
        return count;

CounterModule.getCount();     // Output: 2
CounterModule.count;            //Ouput : 'undefined' as it is a private variable

2. Singleton Pattern: Restricts the instantiation of a class to a single object. It ensures that only one instance of a class is created and provides a global access point to that instance.

var Singleton = (function() {
  var instance;

  function createInstance() {
    var object = new Object("I am the instance");
    return object;

  return {
    getInstance: function() {
      if (!instance) {
        instance = createInstance();
      return instance;

var instance1 = Singleton.getInstance();
var instance2 = Singleton.getInstance();

console.log(instance1 === instance2); // Output: true

3. Factory Pattern: Provides an interface for creating objects but allows subclasses or derived classes to decide which class to instantiate. It abstracts the process of object creation and provides flexibility in object creation logic.

function ShapeFactory() {}

ShapeFactory.prototype.createShape = function(type) {
  switch (type) {
    case 'circle':
      return new Circle();
    case 'rectangle':
      return new Rectangle();
    case 'triangle':
      return new Triangle();
      throw new Error('Invalid shape type');

function Circle() {
  this.type = 'circle';

function Rectangle() {
  this.type = 'rectangle';

function Triangle() {
  this.type = 'triangle';

var factory = new ShapeFactory();
var circle = factory.createShape('circle');
var rectangle = factory.createShape('rectangle');
var triangle = factory.createShape('triangle');

console.log(circle.type);     // Output: circle
console.log(rectangle.type);  // Output: rectangle
console.log(triangle.type);   // Output: triangle

4. Observer Pattern: Establishes a one-to-many relationship between objects, where changes in one object (subject) are automatically reflected in other objects (observers). It enables loose coupling between objects and supports event-driven architectures.

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

Subject.prototype.addObserver = function(observer) {

Subject.prototype.removeObserver = function(observer) {
  var index = this.observers.indexOf(observer);
  if (index !== -1) {
    this.observers.splice(index, 1);

Subject.prototype.notifyObservers = function(data) {
  this.observers.forEach(function(observer) {

function Observer(name) { = name;

Observer.prototype.update = function(data) {
  console.log( + ' received data: ' + data);

var subject = new Subject();
var observer1 = new Observer('Observer 1');
var observer2 = new Observer('Observer 2');


subject.notifyObservers('Hello');  // Output: Observer 1 received data: Hello
                                   //         Observer 2 received data: Hello

These are just a few examples of design patterns in JavaScript. Each pattern has its own purpose and solves specific design problems. It's important to understand their concepts and principles to effectively apply them to different software development scenarios.
Conditional breakpoints can be very useful when debugging JavaScript code, as they allow you to pause execution only when certain conditions are met. Here's how to apply conditional breakpoints in the Chrome browser debugging tool: Open the Chrome DevTools by pressing F12 or right-clicking on a web page and selecting "Inspect". 1) Navigate to the "Sources" tab in the DevTools. 2) Find the JavaScript file that you want to debug in the file tree on the left-hand side. 3) Set a regular breakpoint by clicking on the line number where you want to pause execution. 4) Right-click on the breakpoint and select "Edit breakpoint" from the context menu. 5) In the breakpoint editor, enter the condition that you want to use to trigger the breakpoint. For example, you might enter a variable name and a value to pause execution only when the variable has a specific value. 6) Click "Save" to apply the conditional breakpoint. Now, when the code reaches the line with the conditional breakpoint, it will only pause execution if the condition is true. This can save you time and help you quickly identify issues in your code.
Here's an explanation of Shadow DOM in JavaScript 1. Shadow DOM: Shadow DOM is a web standard that allows for encapsulation of DOM elements within a host element. It provides a way to create a scoped subtree of DOM elements with its own styling and behavior. The encapsulated elements are isolated from the rest of the document, preventing styles and structure from leaking out or being affected by the surrounding page. Example: Let's say we want to create a custom button component that has its own styles and behavior. We can use the Shadow DOM to encapsulate the button's internal implementation.

// Create a custom button element
class CustomButton extends HTMLElement {
  constructor() {
    // Create a shadow root
    const shadow = this.attachShadow({ mode: 'open' });

    // Create a button element
    const button = document.createElement('button');
    button.textContent = 'Click me';
    // Add styles to the button
    const styles = document.createElement('style');
    styles.textContent = `
      button {
        background-color: #e0e0e0;
        color: #333;
        padding: 8px 16px;
        border: none;
        border-radius: 4px;
        cursor: pointer;
      button:hover {
        background-color: #333;
        color: #fff;

    // Append the button and styles to the shadow root

// Define the custom element
customElements.define('custom-button', CustomButton);

Now, when you use the `<custom-button>` element in your HTML, the internal structure and styles defined within the Shadow DOM will be encapsulated and isolated from the surrounding page.

 <!DOCTYPE html> <html> 
   Shadow DOM Example
    My Web Page
 <!-- Custom button element with encapsulated styles -->
 <script src="custom-button.js"></script>

The difference is that compiler usually produces a directly usable artifact (executable binary of some sort). Example: C (produces binary), C# (produces bytecode). Whereas transpiler produces another form of source code (in another language, for example), which is not directly runnable and needs to be compiled/interpreted. Example: CoffeeScript transpiler, which produces javascript. Opal (converts ruby to javascript) Compiler - compiles code to a lower level code. Example:

"Developer code" -> "Machine code"
Java -> bytecode

Transpiler - compiles code to same level of code/abstraction. Example:

"Developer code" -> "Another developer code or version"
JavaScript ES2015+ -> JavaScript ES5

Find the code snippet below: ES6 Code:

const sum=(a)=>{
 return (b)=>{
    return a+b;

ES5 code:

 function sum(a) {
   return function(b) {
      return a+b;  

A regular JavaScript function can be called with the new keyword, for which the function behaves as a class constructor for creating new instance objects. Code e.g:

function Square (length = 10) {
  this.length = parseInt(length) || 10;

  this.getArea = function() {
    return Math.pow(this.length, 2);

  this.getPerimeter = function() {
    return 4 * this.length;

const square = new Square();

console.log(square.length); // 10
console.log(square.getArea()); // 100

Unlike regular functions, arrow functions can never be called with the new keyword because they do not have the [[Construct]] method. As such, the prototype property also does not exist for arrow functions. Sadly, that is very true. Arrow functions cannot be used as constructors. They cannot be called with the new keyword. Doing that throws an error indicating that the function is not a constructor. Also, because arrow functions cannot be called with the new keyword, there is really no need for them to have a prototype. Hence, the prototype property does not exist for arrow functions. Since the prototype of an arrow function is undefined, attempting to augment it with properties and methods, or access a property on it, will throw an error. Code e.g:

const Square = (length = 10) => {
  this.length = parseInt(length) || 10;

// throws an error
const square = new Square(5);

// throws an error
Square.prototype.getArea = function() {
  return Math.pow(this.length, 2);

console.log(Square.prototype); // undefined

Bubble sort and insertion sort are two commonly used sorting algorithms in computer science. Both algorithms work by repeatedly comparing and swapping elements in a list until the list is sorted. Bubble Sort: Bubble sort is a simple sorting algorithm that works by repeatedly swapping adjacent elements if they are in the wrong order. The algorithm takes its name from the way smaller elements "bubble" to the top of the list. Here is how bubble sort works: 1) Start at the beginning of the list. 2) Compare the first two elements. If the first element is greater than the second element, swap them. 3) Move to the next pair of elements and repeat step 2. 4) Continue this process until the end of the list is reached. 5) Repeat steps 1-4 until no swaps are made on a pass through the list. Here is an example of how bubble sort works on a list of numbers:

Unsorted List: 4, 2, 7, 1, 3

Pass 1: 2, 4, 1, 3, 7
Pass 2: 2, 1, 3, 4, 7
Pass 3: 1, 2, 3, 4, 7

The sorted list is 1, 2, 3, 4, 7.

Code for Bubble sort:-

function bubbleSort(arr){
 let n = arr.length;
  for(var i=0;i < n;i++) {
    for(var j=0;j < (n-i-1);j++) {
      if(arr[j] > arr[j+1])  {
       var temp=arr[j];
  return arr;
var arr=[2,3,1,4,7,6];
const sortedArray = bubbleSort(arr);
console.log(sortedArray); //output : [1,2,3,4,6,7]

Time complexity and space complexity of Bubble sort:

 Time complexity: o(n^2) // as we have nested for loops
 Space complexity: o(1) // as we are not storing anything in array, object etc

Insertion Sort: Insertion sort is another simple sorting algorithm that works by building a sorted list one item at a time. The algorithm works by iterating through an unsorted list, and inserting each element into the correct position in a new, sorted list. Here is how insertion sort works: 1) Start at the beginning of the list. 2) Take the next element and insert it into the correct position in the sorted list. 3) Repeat step 2 until all elements have been inserted. Here is an example of how insertion sort works on a list of numbers:

Unsorted List: 4, 2, 7, 1, 3

Pass 1: 2, 4, 7, 1, 3
Pass 2: 2, 4, 1, 7, 3
Pass 3: 2, 1, 4, 7, 3
Pass 4: 1, 2, 4, 7, 3
Pass 5: 1, 2, 4, 3, 7
Pass 6: 1, 2, 3, 4, 7

The sorted list is 1, 2, 3, 4, 7.

Code for Insertion sort:-

function insertionSort(arr) {
  for (let i = 1; i < arr.length; i++) {
    const key = arr[i];
    let j;

    for (j= i - 1; j >= 0 && arr[j] > key; j--) {
      arr[j + 1] = arr[j];

    arr[j + 1] = key;

  return arr;

// Example usage:
const unsortedArray = [64, 34, 25, 12, 22, 11, 90];
const sortedArray = insertionSort(unsortedArray);
console.log(sortedArray); // Output: [11, 12, 22, 25, 34, 64, 90]

Time complexity and space complexity of Insertion sort:

 Time complexity: o(n^2) // as we have nested for loops
 Space complexity: o(1) // as we are not storing anything in array, object etc

Conclusion: As you can see, bubble sort and insertion sort are both effective sorting algorithms, but bubble sort can be less efficient than insertion sort due to its need to perform many swaps, while insertion sort only needs to insert elements into the correct position.
Storing JWT (JSON Web Token) in a cookie is considered safer than storing it in session storage or local storage for several reasons: 1) Cookies are less vulnerable to Cross-Site Scripting (XSS) attacks than session storage or local storage. XSS attacks occur when a malicious script is injected into a website and can access and manipulate data stored in the user's browser. Since cookies have an extra layer of security in the form of the HttpOnly flag, they cannot be accessed by JavaScript code, which reduces the risk of XSS attacks. 2) Cookies can be configured to have an expiration time, after which they are automatically deleted from the user's browser. This means that if an attacker gains access to the JWT stored in a cookie, the token will only be valid for a limited time, reducing the risk of long-term damage. 3) Cookies can be configured to be sent only over HTTPS, which provides encryption and authentication of the data being transmitted. This reduces the risk of man-in-the-middle attacks, where an attacker intercepts and modifies the data being transmitted between the user's browser and the server. 4) Session storage and local storage are more vulnerable to Cross-Site Request Forgery (CSRF) attacks than cookies. CSRF attacks occur when an attacker sends a request from a user's browser without their knowledge or consent. Since session storage and local storage are accessible by JavaScript code, an attacker can easily read and send the JWT token from these storage mechanisms, whereas cookies are less vulnerable to these types of attacks. In summary, storing JWT in a cookie with the HttpOnly flag and an expiration time is considered safer than storing it in session storage or local storage. However, it's important to note that cookies are not immune to attacks, and other security measures such as input validation, access control, and rate limiting should also be implemented to ensure the overall security of the application. Now coming to the next part of question:- No, storing data in a cookie is not 100% safe as cookies, like any other data storage mechanism, can be vulnerable to attacks. However, when used properly, cookies can provide a reasonable level of security. Cookies are vulnerable to attacks such as Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF) attacks, which can compromise the security of the data stored in the cookie. To mitigate these risks, cookies can be configured with various security settings such as the HttpOnly flag, Secure flag, and SameSite attribute, which can make them less vulnerable to attacks. The HttpOnly flag ensures that cookies can only be accessed by the server and not by client-side scripts, which can help prevent XSS attacks. The Secure flag ensures that cookies are only sent over HTTPS, which provides encryption and authentication of the data being transmitted, making them less vulnerable to man-in-the-middle attacks. The SameSite attribute can prevent CSRF attacks by ensuring that cookies are only sent in requests that originate from the same site as the cookie. It's important to note that while cookies can be configured to be more secure, they are not foolproof and can still be vulnerable to attacks. Therefore, it's essential to follow best practices for cookie management, including limiting the amount and sensitivity of data stored in cookies, implementing secure transport protocols such as HTTPS, and regularly monitoring and updating security measures to ensure the ongoing security of the application. Answer for last part of the questions:- If an attacker copies a JWT from a cookie in the browser's debug panel and uses it in a CSRF (Cross-Site Request Forgery) attack, they may be able to impersonate the user and perform unauthorized actions on their behalf. In a CSRF attack, the attacker tricks the user into performing an action on a website without their knowledge or consent. For example, the attacker can create a form on their website that performs an action on the target website, such as transferring funds from the user's account. When the user submits the form, the browser automatically includes the JWT cookie in the request, which allows the attacker to bypass authentication and perform the action on behalf of the user. To mitigate this type of attack, it's important to implement security measures such as CSRF tokens and SameSite cookies. A CSRF token is a unique value that is generated by the server and included in the form, which is verified by the server to ensure that the request is legitimate. SameSite cookies can prevent the browser from including cookies in cross-site requests, which can help prevent CSRF attacks. It's also important to keep the JWT token as short-lived as possible and set appropriate expiration times to limit the window of opportunity for attackers to use the token. Additionally, it's essential to ensure that the JWT token is securely transmitted over HTTPS and that the server-side implementation of the JWT authentication mechanism follows best practices for security and encryption. In summary, while an attacker may be able to copy a JWT from a cookie and use it in a CSRF attack, implementing appropriate security measures such as CSRF tokens and SameSite cookies can help prevent this type of attack.
Quick sort is a popular sorting algorithm that uses the divide-and-conquer approach to sort a list of elements. The algorithm works by selecting a pivot element from the list, partitioning the remaining elements into two sub-lists based on whether they are less than or greater than the pivot, and recursively applying the same process to each sub-list. The algorithm terminates when the sub-lists are of size zero or one, which are by definition sorted. The time complexity of Quick sort depends on the choice of pivot element and the order of the input data. In the best case, where the pivot divides the list into two equal sub-lists, the time complexity of Quick sort is O(n log n), where n is the number of elements in the list. In the worst case, where the pivot is repeatedly chosen as the smallest or largest element, the time complexity of Quick sort can degrade to O(n^2). However, the average time complexity of Quick sort is O(n log n), making it one of the most efficient sorting algorithms.
Steps to get the solution: 1.Loop over the object keys using loop 2. If type of current item is 'object', call flatten method recursively with current item as parameter 3. If type of current item is not an 'object', we can add the current property to result object. Code to flatten the contents of an object :-

let obj = {
const flatten=(obj,result={},keyname="")=>{
 for(var i in obj) {
   if(typeof obj[i]=='object')
   else {
 return result;
           address_country: "india",
	   address_state_city: "pune",
           address_state_srno: "46/1/1/6",
           name: "pravin"