JavaScript Developer Interview Questions For 3 Years Of Experience

The main differences between the HTTP `GET` and `POST` methods are their intended purposes and the way they handle data: 1. Purpose: - `GET`: The `GET` method is used to retrieve data from a server. It is meant for reading or fetching a resource without modifying it on the server. - `POST`: The `POST` method is used to send data to the server to create or update a resource. It is meant for submitting data, such as form submissions, to be processed by the server. 2. Data Handling: - `GET`: Data is appended to the URL as query parameters. For example, `https://example.com/api/users?id=123`. This makes the data visible in the URL and is limited in size due to URL length restrictions. It is suitable for passing small amounts of data, but it's not recommended for sensitive or large data. - `POST`: Data is sent in the body of the HTTP request, which is not visible in the URL. This allows for sending larger amounts of data, and there are no URL length restrictions. It is suitable for sensitive or large data, such as JSON payloads. 3. Caching: - `GET`: `GET` requests can be cached by the browser or intermediate proxies since they are considered safe and idempotent. The same `GET` request can be repeated multiple times without any side effects. - `POST`: By default, `POST` requests are not cached because they may have side effects on the server, such as creating or updating resources. However, caching can be explicitly enabled for `POST` requests using appropriate cache headers. 4. Idempotence: - `GET`: `GET` requests are idempotent, meaning that making the same `GET` request multiple times should have the same result. It should not modify any data on the server. - `POST`: `POST` requests are not idempotent since they typically result in the creation or modification of a resource on the server. Making the same `POST` request multiple times may create multiple resources or have different outcomes. 5. Security: - `GET`: Since `GET` requests append data to the URL, the data becomes visible in browser history, server logs, and can be bookmarked. It is not recommended to send sensitive data via `GET` requests as it can be easily exposed. - `POST`: Data sent via `POST` requests is included in the body and is not directly visible in browser history or server logs, offering better security for sensitive information. Example : Here are examples that demonstrate the difference between the `GET` and `POST` methods: 1. `GET` Method Example: Let's say you have a RESTful API that provides information about users. To retrieve the details of a specific user with the ID "123", you would use a `GET` request. Here's an example using JavaScript's `fetch` API:

   fetch('https://example.com/api/users/123', {
     method: 'GET'
   })
     .then(response => response.json())
     .then(data => {
       console.log(data);
     })
     .catch(error => {
       console.error('Error:', error);
     });

In this example, the `GET` request is made to the URL `https://example.com/api/users/123`, indicating that you want to retrieve user information for the user with the ID "123". The server will respond with the requested user data. 2. `POST` Method Example: Suppose you have a contact form on a website, and you want to send the form data to a server for processing. In this case, you would use a `POST` request to submit the data. Here's an example using JavaScript's `fetch` API:

   const formData = {
     name: 'John Doe',
     email: 'john@example.com',
     message: 'Hello, World!'
   };

   fetch('https://example.com/api/contact', {
     method: 'POST',
     headers: {
       'Content-Type': 'application/json'
     },
     body: JSON.stringify(formData)
   })
     .then(response => response.json())
     .then(data => {
       console.log(data);
     })
     .catch(error => {
       console.error('Error:', error);
     });

In this example, the `POST` request is made to the URL `https://example.com/api/contact` with the form data serialized as JSON in the request body. The server will process the submitted data, such as storing it in a database or sending an email. In summary, the `GET` method is used for retrieving data without modifying it, while the `POST` method is used for sending data to create or update a resource. `GET` requests append data to the URL, have caching advantages, and are idempotent. `POST` requests send data in the request body, are not cached by default, and are not idempotent.
The main differences between the HTTP `POST` and `PUT` methods are their intended purposes and the way they handle data: 1. Purpose: - `POST`: The `POST` method is used to send data to the server to create a new resource. It is often used when submitting forms or sending data that needs to be processed and stored on the server. Each `POST` request typically creates a new resource on the server, and the server assigns a unique identifier to it. - `PUT`: The `PUT` method is used to send data to the server to update an existing resource or create a resource at a specific URL. It is used when you know the exact location of the resource you want to update or create. A `PUT` request can either update an existing resource or create a new one if it doesn't already exist at the specified URL. 2. Idempotence: - `POST`: `POST` requests are not idempotent. Sending the same `POST` request multiple times may result in the creation of multiple resources with different identifiers or cause repeated side effects on the server. - `PUT`: `PUT` requests are idempotent. Making the same `PUT` request multiple times will have the same outcome, ensuring that the resource on the server is updated or created consistently. 3. Data Handling: - `POST`: Data is sent in the body of the HTTP request, typically as form data or serialized JSON/XML. The server processes this data and performs the necessary actions to create a new resource. - `PUT`: Data is sent in the body of the HTTP request, similar to the `POST` method. However, in a `PUT` request, the data represents the complete updated representation of the resource. The server uses this data to replace the existing resource or create a new one. 4. URL Convention: - `POST`: The URL for a `POST` request typically points to a collection endpoint or a general resource endpoint. For example, `https://example.com/api/users` to create a new user. - `PUT`: The URL for a `PUT` request usually points to a specific resource or a unique identifier for that resource. For example, `https://example.com/api/users/123` to update the user with the ID "123". 5. Safe Operations: - `POST`: `POST` requests are not considered safe operations as they can result in the creation of new resources or have side effects on the server. - `PUT`: `PUT` requests are not considered safe operations either, as they can update or create resources on the server. Example: Here are examples that demonstrate the difference between the `POST` and `PUT` methods: 1. `POST` Method Example: Let's say you have an API that handles blog posts. To create a new blog post, you would use a `POST` request. Here's an example using JavaScript's `fetch` API:

   const newPost = {
     title: 'My New Blog Post',
     content: 'This is the content of my new blog post.'
   };

   fetch('https://example.com/api/posts', {
     method: 'POST',
     headers: {
       'Content-Type': 'application/json'
     },
     body: JSON.stringify(newPost)
   })
     .then(response => response.json())
     .then(data => {
       console.log(data);
     })
     .catch(error => {
       console.error('Error:', error);
     });

In this example, the `POST` request is made to the URL `https://example.com/api/posts`, indicating that you want to create a new blog post. The server will process the request, create a new blog post with the provided data (`newPost`), and respond with the created post details. 2. `PUT` Method Example: Suppose you have an API that manages user profiles, and you want to update an existing user's information. To achieve that, you would use a `PUT` request. Here's an example using JavaScript's `fetch` API:

   const updatedUser = {
     name: 'Jane Doe',
     email: 'jane@example.com'
   };

   fetch('https://example.com/api/users/123', {
     method: 'PUT',
     headers: {
       'Content-Type': 'application/json'
     },
     body: JSON.stringify(updatedUser)
   })
     .then(response => response.json())
     .then(data => {
       console.log(data);
     })
     .catch(error => {
       console.error('Error:', error);
     });

In this example, the `PUT` request is made to the URL `https://example.com/api/users/123`, indicating that you want to update the user with the ID "123". The server will process the request, replace the existing user's information with the provided data (`updatedUser`), and respond with the updated user details. Conclusion: In summary, the `POST` method is used to send data to create a new resource, while the `PUT` method is used to send data to update an existing resource or create a new one at a specific URL. `POST` requests create new resources, are not idempotent, and send data representing the resource to be created. `PUT` requests update or create resources, are idempotent, and send data representing the complete updated representation of the resource.
Custom events are events that are created by developers to handle specific scenarios or to extend the capabilities of the built-in events. Custom events allow you to define and trigger events that are not natively available in the browser or the DOM. The ability to create custom events gives you more flexibility in designing event-driven systems and enables you to build modular, reusable components that communicate with each other using custom events. Custom events can be useful in various scenarios, such as: 1. Communication between components: Custom events provide a way for different components of an application to communicate and exchange information. Components can listen for custom events and respond accordingly. 2. Application-level events: You can create custom events to represent application-level events, such as "applicationInitialized" or "userLoggedOut." These events can be triggered at specific points in your application and can be used to trigger actions or update the UI. 3. Event-driven architecture: Custom events facilitate an event-driven architecture, where different parts of your application can be decoupled and communicate through events. This promotes loose coupling and improves the modularity and maintainability of your codebase. To work with custom events, you can use the `CustomEvent` constructor and the `dispatchEvent()` method to create and trigger custom events. Additionally, you can use the `addEventListener()` method to listen for and handle custom events. Here's a step-by-step guide on how to create custom events: 1. Create an event using the `CustomEvent` constructor:

   const myEvent = new CustomEvent('myEvent', {
     detail: { key: 'value' },
     bubbles: true, // Specifies whether the event should bubble up through the DOM tree (optional)
     cancelable: true // Specifies whether the event can be canceled with preventDefault() (optional)
   });

In the example above, we create a custom event named `'myEvent'`. The event can carry additional data in the `detail` property, which is an optional object. The `bubbles` and `cancelable` properties determine the behavior of the event during event propagation and allow for event cancellation if desired. 2. Dispatch the custom event on an element:

   const element = document.getElementById('myElement');
   element.dispatchEvent(myEvent);

In this step, we select the desired HTML element on which the event should be dispatched. Here, we use `document.getElementById('myElement')` to obtain the element with the ID `'myElement'`. Then, we call the `dispatchEvent()` method on the element, passing in the custom event `myEvent` as the argument. 3. Listen for the custom event:

   const element = document.getElementById('myElement');
   element.addEventListener('myEvent', event => {
     console.log('Custom event triggered!', event.detail);
   });

Finally, we register an event listener on the element to capture and handle the custom event. In this example, when the `'myEvent'` event is triggered, the provided callback function will execute. You can access the additional data passed in the event's `detail` property using `event.detail`. You have created a custom event, dispatched it on an element, and set up an event listener to respond to the event. You can adapt this approach to meet your specific use cases and define custom behavior for your events in JavaScript.
In JavaScript, both the `for...in` and `for...of` loops are used for iteration, but they serve different purposes and work with different types of data structures. Here's the difference between them: 1. `for...in` loop: The `for...in` loop is used to iterate over the enumerable properties of an object. It iterates over the keys of an object and provides access to the property names. Here's the basic syntax:

for (variable in object) {
  // code to be executed
}

The `variable` represents the property name in each iteration. Here's an example:

const obj = { a: 1, b: 2, c: 3 };

for (let key in obj) {
  console.log(key); // Outputs: a, b, c
  console.log(obj[key]); // Outputs: 1, 2, 3
}

Note that `for...in` loop can also iterate over the inherited properties of an object. To avoid this, you can use the `hasOwnProperty` method to check if the property is directly defined on the object itself. 2. `for...of` loop: The `for...of` loop is used to iterate over iterable objects like arrays, strings, sets, maps, etc. It provides access to the values of the elements rather than their indices or keys. Here's the basic syntax:

for (variable of iterable) {
  // code to be executed
}

The `variable` represents the value in each iteration. Here's an example:

const arr = [1, 2, 3];

for (let value of arr) {
  console.log(value); // Outputs: 1, 2, 3
}

Unlike the `for...in` loop, the `for...of` loop does not give you access to the keys or indices of the iterable object. It directly provides the values. It's a simpler and more concise way to iterate over arrays and other iterable objects. Conclusion: `for...in` loop is used to iterate over object properties (keys), while the `for...of` loop is used to iterate over iterable objects, providing access to their values.
In JavaScript, when an object is passed as an argument to a function, it is passed by reference. This means that the function receives a reference to the original object, not a new copy of the object. Any modifications made to the object within the function will affect the original object outside of the function as well. Here's an example to demonstrate this behavior:

function modifyObject(obj) {
  obj.property = 'Modified';
}

const myObject = { property: 'Original' };
console.log(myObject);  // { property: 'Original' }

modifyObject(myObject);
console.log(myObject);  // { property: 'Modified' }

In the example above, the `modifyObject` function accepts an object as an argument and modifies its `property` value. When we pass `myObject` to the `modifyObject` function, the function receives a reference to `myObject`. As a result, modifying `obj.property` inside the function also modifies the `property` value of the original object, `myObject`. It's important to note that this behavior applies to objects (including arrays and functions) but not to primitive types like numbers, strings, booleans, etc. Primitive types are passed by value, meaning that a new copy of the value is created and passed to the function. Modifying the value inside the function does not affect the original value outside of the function. For example:
function modifyPrimitive(value) {
  value = 'Modified';
}

let myValue = 'Original';
console.log(myValue);  // 'Original'

modifyPrimitive(myValue);
console.log(myValue);  // 'Original'

In this example, the `modifyPrimitive` function accepts a string as an argument. However, when we modify the `value` parameter inside the function, it does not affect the original value of `myValue` outside of the function. In summary, when passing an object as an argument to a function in JavaScript, it is passed by reference. Any modifications made to the object inside the function will be reflected in the original object outside of the function.
In JavaScript, both `prototype` and `__proto__` (often accessed using the `Object.getPrototypeOf()` and `Object.setPrototypeOf()` methods) are related to the prototype chain and object inheritance, but they have different roles and purposes. 1. `prototype`: The `prototype` property is a property of a constructor function, such as `function Person() {}`, and is used to define and store properties and methods that will be inherited by objects created using that constructor function. It is an object that serves as a blueprint for creating other objects. When you create an instance of a constructor function using the `new` keyword, the newly created object's `__proto__` property is set to the `prototype` property of the constructor function. Example:

function Person(name) {
  this.name = name;
}

Person.prototype.greet = function() {
  console.log(`Hello, my name is ${this.name}.`);
};

const person = new Person('Alice');
person.greet();  // Hello, my name is Alice.

In this example, `Person.prototype` is the prototype object associated with the `Person` constructor function. The `greet()` method defined on `Person.prototype` is then inherited by objects created with `new Person()`. 2. `__proto__`: The `__proto__` property is a property of individual objects that references the prototype of the object. It is a reference to the object's internal [[Prototype]] property. It allows objects to access properties and methods defined on their prototype. Example:

const person = { name: 'Alice' };
console.log(person.__proto__ === Object.prototype);  // true

person.toString();  // inherited from Object.prototype

In this example, `Object.prototype` is the prototype object for all objects created in JavaScript, including `person`. The `__proto__` property of `person` references `Object.prototype`, allowing `person` to access methods like `toString()` defined on `Object.prototype`. It's important to note that while `__proto__` is a non-standard property, it is widely supported in browsers. It has been standardized as `[[Prototype]]` in the ECMAScript specification, and you should prefer using the `Object.getPrototypeOf()` and `Object.setPrototypeOf()` methods to access and set the prototype of an object. In summary, `prototype` is a property of a constructor function used to define properties and methods that will be inherited by instances created with that constructor, while `__proto__` is a property of individual objects that references their prototype in the prototype chain.
Callback hell is a term used to describe a situation in asynchronous programming where code becomes difficult to read, understand, and maintain due to excessive nesting of callback functions. It typically occurs in environments or programming languages that heavily rely on callbacks for handling asynchronous operations, such as JavaScript. In callback-based asynchronous programming, when a task is initiated, instead of waiting for it to complete, the program registers a callback function that will be executed once the task is finished. This allows the program to continue executing other tasks without blocking. However, when multiple asynchronous operations are chained together, the code structure can quickly become convoluted. Callback hell arises when callbacks are nested within other callbacks, leading to deep indentation levels and a loss of code clarity. This nesting occurs when one asynchronous operation depends on the result of another, leading to a cascading effect of callbacks. As more operations are added, the code becomes harder to read, understand, and debug, making it prone to errors and difficult to maintain. Here's an example of callback hell in JavaScript:

asyncOperation1(function (result1) {
  asyncOperation2(result1, function (result2) {
    asyncOperation3(result2, function (result3) {
      // ... and so on
    });
  });
});

In this example, the result of `asyncOperation1` is needed to perform `asyncOperation2`, and the result of `asyncOperation2` is needed for `asyncOperation3`, and so on. As a result, the code becomes deeply nested, making it challenging to follow the logic and leading to a lack of code maintainability. Callback hell can make code harder to debug, test, and reason about, and it can negatively impact the overall development process. To mitigate callback hell, various techniques have been introduced over time, such as Promises, async/await syntax, and the use of libraries like async.js or JavaScript's built-in functions like `Promise.all`. These approaches help to flatten the code structure, make it more readable, and handle asynchronous operations in a more elegant and maintainable manner.
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() {
       count++;
     },
    getCount: function() {
        return count;
   }
  };
})();

CounterModule.increment(); 
CounterModule.increment(); 
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();
    default:
      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) {
  this.observers.push(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) {
    observer.update(data);
  });
};

function Observer(name) {
  this.name = name;
}

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

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

subject.addObserver(observer1);
subject.addObserver(observer2);

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.
In JavaScript, prototypal inheritance is a mechanism through which objects can inherit properties and behavior from other objects. Every object in JavaScript has an internal property called [[Prototype]], which can be accessed using the `Object.getPrototypeOf()` or `__proto__` (deprecated) methods. The [[Prototype]] property refers to another object, often called the prototype object. When a property or method is accessed on an object, and the object itself does not have that property, JavaScript automatically looks up the prototype chain to find the property on the prototype object. Here's an example to illustrate prototypal inheritance:

// Parent object constructor
function Person(name) {
  this.name = name;
}

// Adding a method to the prototype of Person
Person.prototype.greet = function() {
  console.log("Hello, my name is " + this.name);
};

// Creating a new object using the Person constructor
var john = new Person("John");

// Accessing the greet method inherited from the prototype
john.greet(); // Output: Hello, my name is John

In the example above, the `Person` function serves as a constructor for creating person objects. The `Person` constructor has a property `name`, which is assigned when a new object is created using `new Person("John")`. Additionally, we add a `greet` method to the `Person.prototype`. This means that all objects created using the `Person` constructor will inherit the `greet` method. When `john.greet()` is called, JavaScript looks for the `greet` method on the `john` object. Since it's not found, JavaScript follows the prototype chain and finds the `greet` method on the `Person.prototype`. The method is then invoked with `this` referring to the `john` object. Prototypal inheritance allows objects to inherit and share properties and methods from their prototypes. If a property or method is not found on the object itself, JavaScript continues the lookup in the prototype chain until it reaches the end (usually `Object.prototype`) or finds the property/method. This inheritance mechanism provides a flexible and efficient way to reuse code and build complex object hierarchies in JavaScript.
In JavaScript, promises are a programming construct that helps manage asynchronous operations. They provide a way to handle the outcome of an asynchronous task, whether it succeeds or fails, and allow for more structured and readable code compared to using callbacks directly. A promise represents a future value or result of an asynchronous operation. It is an object that can be in one of three states: 1. Pending: The initial state when a promise is created, indicating that the asynchronous operation is still in progress. 2. Fulfilled: The state when the asynchronous operation completes successfully, and the promise is resolved with a value. 3. Rejected: The state when the asynchronous operation encounters an error or fails, and the promise is rejected with a reason or an error object. Promises have two main components: 1. Producer: The code that initiates the asynchronous operation and returns a promise. 2. Consumer: The code that consumes the promise and handles the eventual fulfillment or rejection. Here's an example that demonstrates the basic usage of promises:

// Producer code - Simulating an asynchronous operation
function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const data = { message: "Data fetched successfully" };
      resolve(data); // Promise is resolved with the data
      // reject(new Error("Failed to fetch data")); // Uncomment to simulate a rejection
    }, 2000);
  });
}

// Consumer code - Using the promise
fetchData()
  .then((data) => {
    console.log(data.message);
  })
  .catch((error) => {
    console.error(error);
  });

In this example, the `fetchData` function simulates an asynchronous operation using `setTimeout`. It returns a new promise that wraps the operation and passes in two callback functions, `resolve` and `reject`. Inside the callback, either `resolve` or `reject` is called based on the outcome of the operation. The consumer code uses the promise returned by `fetchData`. It chains a `.then()` method, which is called when the promise is resolved, and a `.catch()` method, which is called when the promise is rejected. If the promise is resolved, the `data` object is logged to the console. If the promise is rejected, the error is logged to the console. Promises offer several advantages over traditional callback-based approaches, such as mitigating callback hell and providing a more structured and readable code flow. Promises can also be chained using multiple `.then()` calls to perform sequential or parallel asynchronous operations. Additionally, promises allow for error handling using `.catch()` or by chaining multiple `.then()` calls and handling errors within them. In modern JavaScript, promises have been further enhanced with the introduction of 'async/await` syntax, which provides a more synchronous-style code structure while still utilizing promises behind the scenes.