Monday 25 November 2019

Most useful and frequently asked interview questions & answers in JavaScript

» History of JavaScript and ECMAScript

» Primitive and Non-Primitive Data Types in JavaScript

In JavaScript, data types can be categorized into two main groups: primitive and non-primitive (also known as reference) types.

» Primitive Data Types

Primitive data types are immutable and stored directly in memory. They represent single values and include the following types:

  • Number: Represents numeric values, including integers and floating-point numbers.
  • String: Represents textual data enclosed within single quotes, double quotes, or backticks.
  • Boolean: Represents a logical value indicating true or false.
  • Undefined: Represents a variable that has been declared but not assigned a value.
  • Null: Represents the intentional absence of any object value.
  • Symbol (added in ECMAScript 6): Represents a unique identifier that is used as a property key.

» Non-Primitive Data Types (Reference Types)

Non-primitive data types are mutable and are accessed by reference rather than by value. They include the following types:

  • Object: Represents a collection of key-value pairs, where each key is a string (or symbol) and each value can be any data type, including other objects.
  • Array: A special type of object used for storing multiple values in a single variable, accessed by numeric indices.
  • Function: A type of object that can be invoked or called to perform a particular task.
  • Date: Represents a specific moment in time, with methods for date and time manipulation.
  • RegExp: Represents a regular expression, used for pattern matching within strings.

Understanding the distinction between primitive and non-primitive data types is essential for proper data manipulation and understanding how data is stored and accessed in JavaScript.

» What is Event Loop?

Imagine you are at a restaurant waiting for your order, and the waiter gives you a buzzer. Instead of standing there staring at the kitchen, you can go sit down, chat with friends, or read a menu. The buzzer will notify you when your order is ready.

JavaScript Execution: Similarly, In JavaScript, when your code is running, it's like the chef in the kitchen preparing your order. If the code has something time-consuming (like fetching data or reading a file), it doesn't want to block everything else. So, it hands off such tasks to the browser's APIs (like the waiter with the buzzer).

Callback Queue: Once the time-consuming task is done (data is fetched or a file is read), it's put in the callback queue. Think of it as the waiter buzzing your buzzer when your order is ready.

Event Loop: The event loop is like the system that keeps checking if there's anything in the callback queue. If there is, it takes it and executes it, just like the waiter buzzing your buzzer and serving your order when it's ready.

Example: Fetching Data: Imagine you have a website that fetches weather data. When you click a "Get Weather" button, JavaScript doesn't want to freeze the entire page waiting for the data. Instead, it uses the Event Loop:

console.log("Button Clicked!");

setTimeout(() => {
  console.log("Fetching Weather Data...");
  // Simulate fetching data from an API
  const weatherData = { temperature: 25, condition: "Sunny" };
  console.log("Weather Data Received:", weatherData);
}, 2000);

console.log("Doing Other Stuff...");

The code runs in the order it's written. The setTimeout function (simulating data fetching) doesn't block other code. When the data is ready, it goes into the callback queue, and the event loop handles it.

Conclusion: The Event Loop ensures that your JavaScript code can efficiently handle time-consuming tasks without freezing the entire application. It keeps the code responsive and allows the browser to do other tasks while waiting for asynchronous operations to complete.

» Is JavaScript single threaded? if Yes then why and how it manages?

Yes, JavaScript is single-threaded, meaning it executes one operation at a time in a single sequence. This might sound limiting, but it simplifies the language and helps avoid complex issues that can arise in multi-threaded environments.

Why Single-Threaded?

Imagine a kitchen with one chef preparing dishes. The chef can only focus on one dish at a time. If multiple chefs try to use the same stove or chop vegetables simultaneously, chaos could ensue – ingredients might get mixed up, and coordination becomes difficult.

1. Avoiding Race Conditions: - In a multi-threaded environment, if two threads try to modify the same variable simultaneously, it can lead to unpredictable results. In JavaScript, being single-threaded helps avoid such race conditions.

2. Simplified Programming Model: - A single thread simplifies the programming model. Developers don't need to worry about synchronization issues, locking mechanisms, or complex thread management.

How It Manages:

Event Loop and Callbacks

JavaScript manages to handle asynchronous tasks and maintain responsiveness through an event-driven model and the concept of callbacks.

Imagine a busy receptionist at a hotel. The receptionist has a list of tasks but can't afford to wait for each task to complete before moving to the next one. Instead, the receptionist takes a task, asks the client to wait (callback), and moves on to the next task. When a task is completed (e.g., room is ready), the receptionist calls back the client.

Similarly in JavaScript:

1. Event Loop: - JavaScript uses an event loop, which continually checks the message queue for new tasks.

2. Callback Queue: - Asynchronous tasks, like fetching data or handling user interactions, are offloaded to browser APIs (e.g., `setTimeout`, HTTP requests).

3. Callback Execution: - When the task completes, a callback is placed in the callback queue.

4. Event Loop Execution: - The event loop constantly checks the callback queue. If it finds a callback, it executes it.

» What is DOM (Document Object Model)?

It is a tree-like structure created by browsers so we can quickly find HTML elements using JavaScript. Manipulating/Changing the DOM means using this API to change the document (add elements, remove elements, move elements around etc...)

» What is Event Delegation and Event Bubbling?

Event delegation allows us to attach a single event listener, to a parent element, that will fire for all descendants matching a selector, whether those descendants exist now or are added in the future.

Event Bubbling means that an event propagates through the ancestors of the element the event fired on.

» What is the difference between display:none and visibility:hidden?

display:none will not be available in the page and does not occupy any space. visibility:hidden hides an element, but it will still take up the same space as before. The element will be hidden, but still affect the layout. visibility:hidden preserve the space, whereas display:none doesn't preserve the space.

» window.onload vs document.onload

window.onload() By default, it is fired when the entire page loads, including its content (images, css, scripts, etc.) In some browsers it now takes over the role of document.onload and fires when the DOM is ready as well.

document.onload() It is called when the DOM is ready which can be prior to images and other external content is loaded.

» What would “1”+2+3 and 1+2+“3” evaluate to, respectively?

"1"+2+3 will display 123 because when the first character is a string, the remaining characters will be converted into a single string, rendering 123

And

1+2+"3" will display 33 because if the string is at the end, the initial characters will perform normal mathematical functions before converting into a string, resulting in 33

» Shallow Copy and Deep Copy

  • Shallow Copy - its slightly opposite of above i.e. even if you copy old variable to new one certain values (sub-values) are still connected to the old variable.
  • Deep Copy - means all the values of old variable are copied to the new variable and changing values from new variable will not affect on old one.
  • Read this article for more details

» POST vs PUT method

  • The POST method is used to create a new resource on the server.
  • POST requests are not idempotent. This means they change the state of the server.
  • If the same POST request is repeatedly sent, it will create multiple of the same resource on the server.
  • Successful POST requests will return a 201 Created status code.
  • PUT requests are used to create/update an entire resource on the server.
  • Unlike POST, PUT is idempotent, meaning it does not affect the server's state after the initial request.
  • This means calling multiple PUT requests will not create multiple of the same resource. Instead, if the resource already exists, it is overwritten. If it does not exist, it is created.
  • Successful PUT requests will return 201 (Created) if the resource does not already exist.
  • If it already exists, a 204 (No Content) or 200 (OK) status is returned to indicate successful completion.

» Call Stack in JavaScript?

JavaScript engine uses a call stack to manage execution context.

In order to keep track of where we are in the program's execution, the call stack is a place where execution contexts are placed on top of each other i.e. the order of execution.

More on this: https://www.geeksforgeeks.org/what-is-the-call-stack-in-javascript

» What is CORS?

“CORS” stands for Cross-Origin Resource Sharing. It allows you to make requests from one website to another website in the browser, which is normally prohibited by another browser policy called the Same-Origin Policy (SOP).

CORS and SOP are both browser policies that have developed in response to issues of browser security and vulnerabilities.

CORS allows servers to specify certain trusted ‘origins’ they are willing to permit requests from. Origins are defined as the combination of protocol (http or https), host (a domain like www.example.com or an IP address) and port. Browsers which implement the CORS policy will include a HTTP header called ‘Origin’ in requests made with AJAX, including above information the value.

CORS issues are framework-agnostic and may occur in any front-end JavaScript application built with plain JS, Angular, React or Vue.js, etc.

Example: Angular CLI provides a development server that runs on localhost:4200 by default so if you are using a back-end server that runs on a different domain, you may have CORS issues if your server is not configured properly. Even if your backend is running on localhost, it will be listenning on a different port, which is treated as a different domain.

In Node.js and Express server, you can use the cors package (npm i cors --save) as follows:

const cors = require('cors'); 
const express = require('express');
const app = express();app.use(cors());

» Javascript Object Creation

There are multiple ways to create object in JavaScript.

// 1) Declaring Object Literal
var myObj = {};

// 2) Using the Object() constructor
var myObj = new Object();

// 3) Using Object.create() method
var myObj = Object.create(null);

» Functions in JavaScript

1) Declared Functions:

function numAdd(a, b){
 return a+b;
 }
 

This function since it declared is actually loaded right when the program is run and it is loaded into the memory and held there until you want to use it.

2) Function Expressions:

var add = function numAdd(a,b){
 return a+b;
 };
 

Function expressions are apply things little bit differently. In function expression we assigned declared function to a variable i.e. add. This particular thing is called function expression and it loads only when this particular line of code is reached inside your program. Since this is an assignment statement we need a semicolon to complete the statement. To call function expression we actually use variable name i.e. add instead of function name. Ex:

add(2,4); // will return 6

3) Anonymous Functions:

Anonymous function is the function expression without name means no need for naming function a second time.

var add = function (a,b){
 return a+b;
 };
 

Here we've removed "numAdd" function name from function expression. We can call this function similar way we call function expression. Ex:

add(2,4) // will return 6

» What is Immediately Invoked Function Expression (IIFE) or Self Invoking Function in JavaScript?

An immediately-invoked function expression (IIFE) immediately calls a function. This simply means that the function is executed immediately after the completion of the definition. It's just as if you assigned it to a variable, and used it right after, only without the variable.

var myAge = 24;

var myVar = (function() {
    var myAge = 25;
    console.log(myAge);
})();

console.log(myAge);

// Output: 

25
24

Here myVar function invoked immediately after completion of its definition so output becomes 25 first and 24 second.

More detaied article on this: IIFE in JavaScript

» What is first class functions in javascript?

aScript, functions are first-class citizens, which means they can be treated just like any other variable. Example:

1. Declaration: Just like you'd declare variables to store names or numbers, you can declare functions to store actions or tasks.

// Function declaration
   function dance() {
       console.log("Let's dance!");
   }

   function sing() {
       console.log("Let's sing!");
   }

2. Assignment: You can assign functions to variables just like you'd assign values.

// Assigning functions to variables
   let activity1 = dance;
   let activity2 = sing;

3. Passing as Arguments: You can pass functions as arguments to other functions.

// Function taking another function as an argument
   function perform(activity) {
       activity();
   }

4. Returning from Functions: Functions can also return other functions.

// Function returning another function
   function createActivity(type) {
       if (type === 'dance') {
           return dance;
       } else {
           return sing;
       }
   }

5. Usage: You can then use these functions just like any other variable.

  // Using functions
   perform(activity1); // Output: Let's dance!
   perform(activity2); // Output: Let's sing!

   let newActivity = createActivity('dance');
   newActivity(); // Output: Let's dance!

In this example, functions like `dance` and `sing` are treated as first-class citizens. They can be assigned to variables, passed as arguments, returned from other functions, and used just like any other data. This flexibility makes JavaScript a powerful language for creating dynamic and expressive code.

» Explain Server-Sent Events (SSE)

Server-Sent Events (SSE) is a web technology that enables servers to push real-time updates to web clients over HTTP. It allows the server to send data to the client once a connection is established, and the connection remains open for the duration of the stream.

Here's a short example to illustrate how SSE works:

Server-side (Node.js example using Express):

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

app.get('/events', (req, res) => {
    // Set headers to indicate SSE
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    // Send data to client every second
    const intervalId = setInterval(() => {
        const eventData = {
            message: 'This is a server-sent event!',
            timestamp: new Date().toISOString()
        };
        res.write(`data: ${JSON.stringify(eventData)}\n\n`);
    }, 1000);

    // Send a heartbeat every 20 seconds
    const heartbeatId = setInterval(() => {
        res.write(':\n\n');
    }, 20000);

    // Handle client disconnect
    req.on('close', () => {
        clearInterval(intervalId);
        clearInterval(heartbeatId);
        console.log('Client disconnected');
    });
});

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

Client-side (HTML + JavaScript):

<body>
    <div id="events"></div>

    <script>
        const eventsDiv = document.getElementById('events');
        const eventSource = new EventSource('/events');

        eventSource.onmessage = (event) => {
            const eventData = JSON.parse(event.data);
            eventsDiv.innerHTML += `<p>${eventData.message} (${eventData.timestamp})</p>`;
        };

        eventSource.onerror = (error) => {
            console.error('EventSource error:', error);
        };
    </script>
</body>

In this example:

- The server sets up an endpoint (`/events`) to handle SSE connections.

- When a client connects to `/events`, the server sends a stream of events every second, along with a heartbeat message to keep the connection alive.

- The client listens for incoming events using the `EventSource` API and updates the UI accordingly.

Server-sent events are useful for scenarios where real-time updates from the server to the client are needed, such as chat applications, live scoreboards, or stock tickers.

» What is a closure?

  • A closure is an inner function that has access to the outer (enclosing) function’s variables—scope chain.
  • The closure has three scope chains:
  • It has access to its own scope (variables defined between its curly brackets)
  • It has access to the outer function’s variables, and
  • It has access to the global variables.

Example:

function closureTest(){
 var x=4;
 return x;
}

Here, if I call closureTest(); it'll return 4

but if I try console.log(x); it'll return undefined because x is only accessible inside closureTest() function.

Now lets make it slight more complex

function closureTest(){
     var x=4;
     // The inner function can access the outer function's variables, because they feel like global variables
     function xClose(){    
      return x;
     }
     return xClose();
    }
    

Lets call it

var checkLocalX=closureTest();
checkLocalX(); // Ouput is 4

Eventhough closureTest() has finished operating, its local variable is now bound within checkLocalX.

More examples of closure:

function myCar(carName) {
  var modelName = "Audi A6";

  function myCarColor(colorName) {
    console.log("Name: ", carName);
    console.log("Model: ", modelName);
    console.log("Color: ", colorName);
  }

  myCarColor("Red");
}

myCar("Audi");

// Here, myCarColor can access the "carName" variable which is defined as an argument to "myCar" function. 

// Also it can access modelName from "myCar" function

// This is closure

Closure memory efficiency example:

// Closure memory efficiency example

function getBigData(index){
    const array = new Array(5000).fill('Hello');
     console.dir("created..!!!");
     return array[index];
   }
   
   getBigData(600);
   getBigData(1200);
   getBigData(2400);
   
   // In first example we are creating the array 3 times
   
   function getBigData2(){
    const array = new Array(5000).fill('Hello');
     console.dir("Created again...");
     return function(index){
      array[index];
     } 
   }
   
   let data = new getBigData2();
   
   data(600);
   data(1200);
   data(2400);
   
   
   // But in first example we have created it only once and then maintaining it in 
   // the closure scope. 
   // It just saving our creation process hence it'll take less memory as compare to 
   // first example.
   

» What are the advantages and disadvantages of closures in JavaScript?

Closure advantages:

  • Closure enables the use of nested functions that are used to get the values created in the execution context of that of the parent function.
  • They can prove as a perfect solution while solving a problem of hierarchy in any program.
  • They have huge importance as far as functional programming is concerned. It solves the for loop problem of functional programming.
  • It is also used to simulate private scope for variables.

Closure disadvantages:

  • One the major drawback of using closures is that, it consumes more memory and if not used properly can lead to ‘memory leaks’. Memory leak can be defined as a memory which is no more required by the application is not released.
  • Till the time its active, the memory can’t be garbage collected.
  • It slows down the performance, because function within other function creates duplicate in memory.

» What is JavaScript Hoisting?

In JavaScript Hoisting is the concept of program load order. Load order ensures that every line of code can execute when it's needed.

Example: We build a function like this:

function sumOfNum(x,y){
 var result=add(x*x, y*y);
 return result;

 function add(a,b){
  var c=a+b;
  return c;
 }
}

But JavaScript compiler actually loads it like this:

function sumOfNum(x,y){
 var result=undefined;  // given memory space at the beginning of scope initialization but it has no value. The very first value it has is undefined
 function add(a,b){ //next is whole declared function hoisted on top of scope and sits there in memory waiting for us to use it
  var c=a+b;
  return c;
 }
 result=add(x*x, y*y); //finally executable code will run
 return result;
}

Because declared stuff that needs space in memory is first "hoisted" to the top of the scope before any operational code run.

  • 1. Variable Assignment
  • 2. Function Declaration
  • 3. Variable Declaration

More detailed article on this: Understanding JavaScript Hoisting

» Explain Prototype and Inheritance in JavaScript?

The Object's parent is called it's Prototype.

Following are some of the properties of object in javascript:

  • constructor
  • valueOf
  • toLocaleString
  • toString
  • propertyIsEnumerable
  • isPrototypeOf
  • hasOwnProperty

All the above properties are belong to or come from the Object's Prototype.

When a generic object is created, its properties passes it many important properties.

A Prototype is like a blueprint object for the object we are trying to create.

So the Object Prototype (Highest Prototype) passess all the properties to object and that is called "Inheritance".

Inheritance helps avoid over-coding multiple properties and methods into similar objects. So the object we create inherits directly from the highest level of javascript hierarchy i.e. the Object Prototype.

Similarly all the native JavaScript data structures inherit all of their properties and methods from their own prototypes. Like,

  • Array inherits from Array Prototype, so all the array methods & properties like push(), pop(), length, shift(), join(), reduce(), sort(), slice(), join() these all come from Array Prototype.
  • String inherits from String Prototype, so all the string methods & properties like concat(), trim(), length, toLowerCase(), toUpperCase(), substring(), replace(), indexOf(), these all come from String Prototype.
  • Number inherits from Number Prototype, so all the number methods & properties like toFixed(), toExponential(), toPrecision() these all come from String Prototype.
  • Function inherits from Function Prototype, so all the function methods & properties like name, bind(), call(), apply(), these all come from Function Prototype.
  • And all these above (lower level) prototypes (Array, String, Number, Function) inherit from the Object Prototype. 
  • That means all these prototypes have access of Object properties.
var myString="Hello World";

myString.length;
myString.trim();
myString.indexOf();
myString.toString(); // Can access object prototypes method
myString.valueOf(); // Can access object prototypes method

Because Object Prototype is Ancestor of String Prototype and myString variable.

In JavaScript, the general object is what is commonly known as a Constructor or a Prototype.

Also in JavaScript, there is no real difference between a regular function and a constructor function. But in order to help developers differentiate between the two, constructor functions are capitalized.

By creating a prototype, developers can easily create multiple unique instances of the prototype. This is a create way to keep our code clean and concise.

Lets see an example below

// CREATE A NEW FUNCTION NAME : "SPORT"

function Sport(sportName, playerName){
  this.sportName = sportName;
  this.playerName = playerName;
}

// NOW LETS INHERIT "SPORT" WITH ANOTHER FUNCTION

// Inheritance is the process by which one object can be based on another. 
// This lets the objects to share each other’s properties.

Sport.prototype.watch = function(){
  console.log("Watching "+this.sportName+" now");
  console.log("My favourite player is "+this.playerName);
}

// NOW LETS INSTANTIATE THIS

var cricket = new Sport("Cricket", "Sachin Tendulkar");
var tennies = new Sport("Tennies", "Roger federer");


console.log(cricket.watch());
console.log(tennies.watch());

// By placing watch on Sport.prototype, 
// we have made it available to all instances of Sport.

When the JavaScript engine comes across cricket.watch() in the code, it looks for the property called watch inside the cricket object. When it doesn’t find one, it will look up the prototype chain to cricket’s parent Sport.prototype. There it will find Sport.prototype.watch and calls it with a this that is bound to cricket. Same goes for the tennies object.

ES6 version

// Base class
class Sport{
  constructor(playerName){
    this.playerName = playerName;
  }

  getPlayerName(){
    return this.playerName;
  }
}

// Inherited class
class Cricket extends Sport{
  constructor(playerName){
    super(playerName);
  }

  getInfo(){
    return "My favourite player is: "+super.getPlayerName();
  }
}

const obj = new Cricket("Sachin Tendulkar");

console.log(obj.getInfo());

We use extends to inherit from another class and the super keyword to call the parent class (function).

» Explain "Short-Circuiting" in JavaScript?

Like many other languages Javascript’s && and || operators short-circuit evaluations, that is, for && if the first operand evaluates to false, the second operand is never evaluated because the result would always be false. Similarly, for || if the result of the first operand is true, the second operand is never operated. Example:

var myVar=true; 
myVar=myVar || null;
console.log(myVar);

Output: true;

var myVar=false; 
myVar=myVar || null;
console.log(myVar);

Output: null;

var myVar=undefined; 
myVar=myVar || null;
console.log(myVar);

Output: null;

Which means as soon as something "not false" is found, it is accepted for the assignment. This is called Short-Circuiting.

» var vs let in JavaScript

"let" variables are scoped to the nearest block (a block is any code section with curly braces, like if,else,for, while etc.) and are not hoisted. Using let instead of var prevents variable declarations from being moved to the top of the scope on hoisting.

var has some unexpected behaviour inside for-in loop.

Example:

function getUserProfile(usersArr){
 for(var i in usersArr){
    //calling fetchProfile function to fetch users data. It has two args 1. server url to grab data & 2. callback when we got response from server
    fetchProfile("/users/"+usersArr[i], function(){
     console.log("Fetched ", usersArr[i]); //we are accessing i inside callback  }
   }
  }
  

When we call getUserProfile(['virat','rohit','ashwin','jadeja']; we'll get an output like

Fetched jadeja

Fetched jadeja

Fetched jadeja

Fetched jadeja

This is unexpectedly outputs the same value on all iterations because here first of all var i is hoisted to the top of the function and shared across each iteration of the loop which means we've same i variable so when callbacks begin to run, i holds the last value assigned to it from the for loop. We can solve this by just replacing var keyword with let. Example:

function getUserProfile(usersArr){
 for(let i in usersArr){
    //calling fetchProfile function to fetch users data. It has two args 1. server url to grab data & 2. callback when we got response from server
    fetchProfile("/users/"+usersArr[i], function(){
     console.log("Fetched ", usersArr[i]); //we are accessing i inside callback  }
   }
  }
  

Now When we call getUserProfile(['virat','rohit','ashwin','jadeja']; we'll get an output like:

Fetched virat

Fetched rohit

Fetched ashwin

Fetched jadeja

With let, there is no sharing in for loops. A new variable is created on each iteration. So each callback function holds a reference to their own version of i.

Variable declared with "let" can be reassigned, but cannot be redeclared within the same Scope.

let sport="cricket";
sport="football";
// Reassigning is allowed

let sport="cricket";
let sport="football";
// Redeclaring is not allowed thus it'll throw an error : TypeError: Identifier 'sport' has already declared

Now lets see another example:

let myVar = "hello";                     // Global Scope
            function myFun(){
                let myVar = "world";            // Local Scope
                        console.log(myVar);
                }            
        console.log(myVar);
        myFun();
    
    //Output:
    Hello
    World
    

In above example though we are using same variable "myVar" it will not throw any error because we are using it within different scope.

More Ref: let vs var in JavaScript

» How to reverse string in JavaScript without using reverse() function?

<script type="text/javascript">    
    /*
         ***************** Example 1******************
         */
        var string="kunal jog";
        var len=string.length;
        var count="";
        for(var i=(len-1); i>=0;i--){
            count+=string[i];
        }
        //alert(count);    
        console.log(count);
            
            
        /*
         ***************** Example 2******************
         */
        var string="javascript";
        var len=string.length;
        var count="";
        for(var i=1; i<=len; i++){
            count+=string[len-i];
        }
            
        //alert(count);    
        console.log(count);    
    </script>
    

» How to count string length in JavaScript without using length property?

<script type="text/javascript">        
    var string="JavaScript";
    var count=0;    
    while(string[count] != undefined){
        count++;
    }    
    //alert(count);
    console.log(count);
</script>

» String Palindrome program in JavaScript?

<script type="text/javascript">
function isPalindrome(str) {
    str = str.replace(/\W/g, '').toLowerCase();
    alert(str);
    return (str == str.split('').reverse().join(''));
}

console.log(isPalindrome("level"));                   // logs 'true'
console.log(isPalindrome("levels"));                  // logs 'false'
console.log(isPalindrome("A car, a man, a maraca"));  // logs 'true'
</script>
function isPalindrome(str) {
  // Convert the string to lowercase to ignore case sensitivity
  str = str.toLowerCase();
  
  // Define two pointers, one starting from the beginning of the string and the other from the end
  let left = 0;
  let right = str.length - 1;
  
  // Iterate over the string until the two pointers meet
  while (left < right) {
    // Ignore non-alphanumeric characters by skipping them
    while (!isAlphanumeric(str[left])) {
      left++;
    }
    while (!isAlphanumeric(str[right])) {
      right--;
    }
    
    // If characters at the two pointers don't match, the string is not a palindrome
    if (str[left] !== str[right]) {
      return false;
    }
    
    // Move the pointers towards the center
    left++;
    right--;
  }
  
  // If the loop completes without finding any mismatch, the string is a palindrome
  return true;
}

// Helper function to check if a character is alphanumeric
function isAlphanumeric(char) {
  return /[a-z0-9]/.test(char);
}

// Test cases
const str1 = "A man, a plan, a canal, Panama!";
const str2 = "racecar";
const str3 = "hello";
console.log(`${str1} is a palindrome:`, isPalindrome(str1));
console.log(`${str2} is a palindrome:`, isPalindrome(str2));
console.log(`${str3} is a palindrome:`, isPalindrome(str3));

» Compare two arrays and find difference between them?

<script type="text/javascript">
    var array1 = [1, 2, 3, 4, 5, 6];
    var array2 = [1, 2, 3, 4, 5, 6, 7, 8, 9];

    var result = [];

    /***********Method 1************/
    var count = 0;
    $.grep(array2, function (index) {
        console.log(index);
        if ($.inArray(index, array1) == -1)
            result.push(index);
        count++;
    });
    console.log(" the difference is " + result);


/************Method 2**************/
    var len = array2.length;
    for (var i = 0; i <= len - 1; i++) {
        if (array1.indexOf(array2[i]) == -1) {
            result.push(array2[i]);
        }
    }
    console.log(" the difference is " + result);


/************Method 3**************/
    var diff = $(array2).not(array1).get();
    alert(diff);
</script>

» How to empty an array in JavaScript?

<script type="text/javascript">

/* Example: Consider following array */

var arrayList =  ['a','b','c','d','e','f'];

/* There are a couple ways we can use to empty an array */

arrayList = []     // Method 1

arrayList.length = 0;    // Method 2

arrayList.splice(0, arrayList.length);      // Method 3

</script>

» What will be the output of below code:

var variable = 10;

(() =>{

console.log(variable);

variable = 20;

console.log(variable);

})(); //iife

console.log(variable);

var variable = 30;

Output:

10

20

20

Inside the IIFE, console.log(variable) logs the value of variable, which is 10 at that point in time.

Then, variable is reassigned a value of 20 inside the IIFE.

After the IIFE, console.log(variable) logs the current value of variable, which is 20.

Finally, outside the IIFE, the variable variable is declared and assigned a value of 30, but since it's already been declared, it simply updates the value of the existing variable. Therefore, the value of variable remains 20 when logged to the console the second time outside the IIFE.

» What will be the output of below code:

var fullname="hello world";

var obj = {
	fullname:"Hacked full name",	
	prop:{
		fullname: "Inside Prop",
		getFullname: function(){
			return this.fullname;
		}
	},
	getFullname: function(){
		return this.fullname;
	},
	getFullnameV2: () => this.fullname,
	getFullnameV3: (function(){
		return this.fullname;
	})()
}

console.log(obj.prop.getFullname());
console.log(obj.getFullname());
console.log(obj.getFullnameV2());
console.log(obj.getFullnameV3());

OUTPUT:

Inside prop

Hacked full name

undefined

TypeError: obj.getFullnameV3 is not a function

(NOTE: if you run getFullnameV2: () => this.fullname this on browser output will be "hello world");

-

1. console.log(obj.prop.getFullname()); - This logs "Inside Prop". The getFullname function is called as a method of obj.prop, so this inside getFullname refers to obj.prop.

2. console.log(obj.getFullname()); - This logs "Hacked full name". The getFullname function is called as a method of obj, so this inside getFullname refers to obj.

3. console.log(obj.getFullnameV2()); - This logs undefined. The getFullnameV2 function is an arrow function, which does not have its own this context. Therefore, this inside getFullnameV2 refers to the global context (which is window in a browser and global in Node.js). Since there is no global variable named fullname, it returns undefined.

4. console.log(obj.getFullnameV3());: This attempts to call `getFullnameV3`, which is assigned the result of an immediately-invoked function expression (IIFE). However, `getFullnameV3` is not defined as a function, but rather as the result of executing the IIFE. Since the IIFE doesn't return a function, `obj.getFullnameV3` is not a function, resulting in a `TypeError`.

» What will be the output of below code:

const myObj = {
	name : "john doe",
  sayName: function(){
  	console.log(this.name);
  }
}

setTimeout(myObj.sayName, 3000);

The output would be `undefined`.

This happens because when you pass myObj.sayName as a callback to `setTimeout`, it loses its context (`this`). So, when `sayName` is invoked by `setTimeout`, `this` inside `sayName` refers to the global object (in non-strict mode) or `undefined` (in strict mode), rather than `myObj`. Since `name` is not defined in the global scope or as a property of `undefined`, `this.name` evaluates to `undefined`.

To ensure that the sayName function retains its context and refers to myObj, you can use either of the following methods:

setTimeout(myObj.sayName.bind(myObj), 1000);

OR

setTimeout(() => myObj.sayName(), 1000);

Both of these methods explicitly set the context (this) of the sayName function to myObj, ensuring that this.name refers to the name property of myObj.

» What will be the output of below code:

Object.create() is a method in JavaScript that creates a new object, using an existing object as the prototype of the newly created object. Here's how it works:

const parentObj = {
  greeting: 'Hello'
};

const childObj = Object.create(parentObj);

console.log(childObj.greeting); // Outputs: Hello

In this example, `childObj` is created with `parentObj` as its prototype. This means that `childObj` inherits properties and methods from `parentObj`. However, `childObj` itself does not have its own properties or methods; it delegates property access to its prototype, `parentObj`.

On the other hand, normal object declaration creates an object with its own properties and methods directly:

const obj = {
  greeting: 'Hello'
};

console.log(obj.greeting); // Outputs: Hello

In this case, `obj` has its own property `greeting`, which is directly defined within the object.

So, the key difference is that Object.create() creates an object with a specified prototype, while normal object declaration creates an object with its own properties and methods directly. Object.create() is useful when you want to create objects with shared behavior through prototype inheritance.

» What is Memoization?

Memoization is an optimization technique used primarily to speed up computer programs by having function calls avoid repeating the calculation of results for previously processed input.

Example:

<script type="text/javascript">
var cachedResult;
function doHeavyCalculation()
{
    if (typeof(cachedResult) !== 'undefined')
        return cachedResult;

    // no cached result available. calculate it, and store it.
    cachedResult = /* do your computation */;
    return cachedResult;
}
</script>

Example of a function that calculates the square of a number. We'll implement the function without memoization first, and then optimize it using memoization.

// Function to calculate the square of a number without memoization
function squareWithoutMemoization(n) {
  console.log(`Calculating square of ${n}`);
  return n * n;
}

console.log(squareWithoutMemoization(5)); // Output: Calculating square of 5 \n 25
console.log(squareWithoutMemoization(5)); // Output: Calculating square of 5 \n 25

In the above example, every time we call squareWithoutMemoization(5), the function recalculates the square of 5, even though it's the same input.

// Function to calculate the square of a number with memoization
const squareMemoized = (function() {
  const memo = {};
  return function(n) {
    if (n in memo) {
      console.log(`Using memoized result for ${n}`);
      return memo[n];
    } else {
      console.log(`Calculating and caching square of ${n}`);
      memo[n] = n * n;
      return memo[n];
    }
  };
})();

console.log(squareMemoized(5)); // Output: Calculating and caching square of 5 \n 25
console.log(squareMemoized(5)); // Output: Using memoized result for 5 \n 25

In this memoized version, we create a closure that maintains a memo object to store the results of previous function calls. Before calculating the square of a number, we check if it exists in the memo object. If it does, we return the cached result directly. Otherwise, we calculate the square, cache it, and return it.

By memoizing the function, we eliminate redundant calculations for the same inputs, improving performance, especially for repetitive function calls with the same arguments.

You can use memoization in scenarios where you have expensive function calls with repeated inputs, such as recursive functions, complex mathematical computations, or any computation-intensive tasks where the same input is expected to occur frequently.

Reference: http://stackoverflow.com/questions/20103739/javascript-memorization

» What is JQuery Method Chaining?

Chaining allows us to run multiple jQuery methods (on the same element) within a single statement.

//Example without chaining:

$("#p1").css("color","red);
$("#p1").slideUp(2000);
$("#p1").slideDown(2000);


//Example with chaining:

$("#p1").css("color", "red").slideUp(2000).slideDown(2000); //single statement

Notice that with chaining, the #p1 only needs to be selected one time, whereas without chaining, jQuery must search the whole DOM and find the #p1 before each method is applied. Thus, in addition to yielding more concise code, method chaining in jQuery offers a potentially powerful performance advantage.

» Difference between null and undefined in JavaScript

// Example:

typeof null; // "object"
typeof undefined; // "undefined"

var a;  
var b = null;  
a == b; // "true" because their values are the same  
a === b; // "false". they have different types

» Javascript call() & apply() vs bind()?

var obj={num:2};
var addToThis=function(a, b){
 return this.num+a+b;
};

/* .call() attaches a function to object temporarily,
runs it and give back as a part of object. 
It attaches "this" into function and executes the function immediately */
alert(addToThis.call(obj,4,8));


/* .apply() is similar to call except that it takes 
an array-like object instead of listing the arguments 
out one at a time */
var arr = [4,8];
alert(addToThis.apply(obj,arr));


/* .bind() attaches "this" into function and it needs to be invoked separately like this: */

var add=addToThis.bind(obj);
alert(add(4,8));
// or we can also it like this
var add=addToThis.bind(obj,4,8);
alert(add());

call() & apply() - are useful for borrowing methods while apply() is useful for us to call a function later on with certain context (or certain this keyword). See example below:

var mySport = {
 name: "Cricket",
 matchesWon: 5,
 getData(num1, num2){
  return this.matchesWon += num1+num2;
 }
}

var mySport1 = {
name: "Football",
 matchesWon: 5
}

// So lets say I want to borrow a function getData() from mySport object to mySport1 object then

mySport.getData.call(mySport1,10,10);

console.log("Call ex => ", mySport1);

// result would be: 
// {"name":"Football","matchesWon":25}

// Apply is the same only it takes arguments as an array, Ex:

mySport.getData.apply(mySport1,[20,20]);

console.log("Apply ex => ", mySport1);

// result would be: 
// {"name":"Football","matchesWon":65}


// Lets see bind() now. 

// Unlike call() and apply() bind returns a new function 
// with a certain context and parameters instead of immediatelly calling it.

mySport.getData.bind(mySport1,30,30);

// now if I do console then it won't work
console.log("bind without function call ex => ", mySport1);

// result would be: 
// {"name":"Football","matchesWon":65}

// but if I do like this

var newSport = mySport.getData.bind(mySport1,30,30);
newSport();

console.log("bind with function call ex => ", mySport1);

// result would be: 
// {"name":"Football","matchesWon":125}

» NaN and isNaN() in JavaScript

  • NaN is return value of a number which have an undefined numerical result
  • Ex. typeof NaN // output will be Number
  • NaN can happen if you divide number zero by zero (0/0), if you try to convert undefined or non-numeric string into a number, any operation where NaN is an operand etc..
  • isNaN - It checks whether the value is Not a Number which means illegal number.
  • It returns true of value is not a number
isNaN('Hello') //true
isNaN(712) //false
isNaN(-2) //false
isNaN(10-5) //false
isNaN(0) //false
isNaN('1') //false
isNaN('') //false
isNaN(true) //false
isNaN(undefined) //true
isNaN('NaN') //true
isNaN(NaN) //true
isNaN(0 / 0) //true

» What is Higher Order Function in JavaScript?

A higher-order function in JavaScript is a function that either takes another function as an argument or returns a function as its result.

Lets see some examples:

Returns a function as its result: Similarly, if you have a function that creates and returns another function, then the outer function is considered a higher-order function. For example:

function greeter(greeting) {
    // This is a higher-order function because it returns a function
    return function(name) {
        console.log(greeting + ", " + name);
    }
}

const sayHello = greeter("Hello");
sayHello("Alice"); // Output: Hello, Alice

Here, greeter is a higher-order function because it returns a new function (function(name) {...}).

One more example

// Higher-order function
function operation(number, doAdd) {
    return doAdd(number);
}

// Callback function
function add(num) {
    return num + num;
}

// Using the higher-order function
const result = operation(4, add);
console.log(result); // Output: 16

Higher-order function (operation):

operation is a higher-order function because it takes another function (doAdd) as an argument. It also takes a number as its first argument. Inside operation, it simply calls the function passed (doAdd) with the number as an argument and returns the result. Callback function (add):

add is a simple function that takes a number and returns the sum of that number with itself (num + num). This function is passed as an argument to the higher-order function (operation). Using the higher-order function:

We call operation(4, add), passing 4 as the number and add as the doAdd function. Inside operation, it calls add(4), which computes 4 + 4 and returns 8. So, result will be 8, and when we log result, it prints 8 to the console.

» Explain “this” keyword.

The “this” keyword refers to the object that the function is a property of.

The value of the “this” keyword will always depend on the object that is invoking the function.

Confused? Let’s understand the above statements by examples:

function doSomething() {
  console.log(this);
}
   
doSomething();

What do you think the output of the above code will be?

Note - Observe the line where we are invoking the function.

Check the definition again:

The “this” keyword refers to the object that the function is a property of.

In the above code, the function is a property of which object?

Since the function is invoked in the global context, the function is a property of the global object.

Therefore, the output of the above code will be the global object. Since we ran the above code inside the browser, the global object is the window object.

Example 2:

var obj = {
    name:  "cricket",
    getName: function(){
    console.log(this.name);
  }
}
   
obj.getName();

In the above code, at the time of invocation, the getName function is a property of the object obj , therefore, this keyword will refer to the object obj, and hence the output will be “cricket”.

Example 3:

 var obj = {
    name:  "cricket",
    getName: function(){
    console.log(this.name);
  }    
}
       
var getName = obj.getName;
       
var obj2 = {name:"Tennies", getName };
obj2.getName();

Can you guess the output here?

The output will be “Tennies”.

Although the getName function is declared inside the object obj, at the time of invocation, getName() is a property of obj2, therefore the “this” keyword will refer to obj2.

The silly way to understand the “this” keyword is, whenever the function is invoked, check the object before the dot. The value of this . keyword will always be the object before the dot.

If there is no object before the dot-like in example1, the value of this keyword will be the global object.

Example 4:

var obj1 = {
    address : "Mumbai,India",
    getAddress: function(){
    console.log(this.address); 
  }
}
   
var getAddress = obj1.getAddress;
var obj2 = {name:"Tennies"};
obj2.getAddress();    

Can you guess the output?

The output will be an error.

Although in the code above, this keyword refers to the object obj2, obj2 does not have the property “address”‘, hence the getAddress function throws an error.

» What is temporal dead zone in JavaScript?

Temporal Dead Zone is a behaviour that occurs with variables declared using let and const keywords. It is a behaviour where we try to access a variable before it is initialized. Examples of temporal dead zone:

console.log(x); // Output: ReferenceError: Cannot access 'x' before initialization
let x = 10;

x is declared with let, so it's hoisted to the top of its scope.

However, trying to access x before its declaration results in a ReferenceError because it's still in the Temporal Dead Zone until the interpreter reaches its declaration.

» What would the output of above code and why?

let x= {}, y = {name:"Ronny"},z = {name:"John"};

x[y] = {name:"Kunal"};

x[z] = {name:"Akki"};

console.log(x[y]);

Output:

In this code snippet, we are declaring three objects x, y, and z, and assigning them different properties. Then, we're using y and z as keys to set properties on the object x. Finally, we're logging the value of x[y].

Let's break down the code:

We have three objects declared:

x is an empty object.

y is an object with a property name set to "Ronny".

z is an object with a property name set to "John".

We're using y and z as keys to set properties on the object x:

x[y] sets a property on x with the key "[object Object]" (because when an object is used as a key, it is implicitly converted to a string), and the value is an object {name:"Kunal"}.

x[z] also sets a property on x with the key "[object Object]", and the value is an object {name:"Akki"}. Finally, we're logging the value of x[y], which is accessing the property in x with the key "[object Object]".

The output will be:

{name: "Akki"}

Why?

When we use an object as a key in an object in JavaScript, it is implicitly converted to a string, which is "[object Object]".

So, both x[y] and x[z] are setting properties on x with the same key "[object Object]".

The second assignment x[z] overrides the value set by the first assignment x[y].

Hence, when we log x[y], we're actually accessing the value set by the second assignment, which is {name:"Akki"}.

» What would the output of above code and why?

function runFunc(){
  console.log("1" + 1);
  console.log("A" - 1);
  console.log(2 + "-2" + "2");
  console.log("Hello" - "World" + 78);
  console.log("Hello"+ "78");
}
runFunc();

Output:

11
NaN
2-22
NaN
Hello78

1) console.log("1" + 1);: Here, "1" is a string, and 1 is a number. When you use the + operator with a string and a number, JavaScript converts the number to a string and performs string concatenation. So, "1" + 1 results in "11".

2) console.log("A" - 1);: Here, "A" is not a valid number, so JavaScript tries to convert it to a number. Since "A" cannot be converted to a number, the operation results in NaN (Not a Number).

3) console.log(2 + "-2" + "2");: JavaScript evaluates expressions from left to right. 2 + "-2" first performs addition, which results in a string "2-2". Then, "2-2" + "2" performs string concatenation, resulting in "2-22".

4) console.log("Hello" - "World" + 78);: Here, we're trying to subtract two strings ("Hello" and "World") which are not valid numbers, so JavaScript returns NaN. Then, NaN is concatenated with 78, resulting in "NaN78".

5) console.log("Hello" + "78");: This simply concatenates the two strings "Hello" and "78", resulting in "Hello78".

» What would the output of above code and why?

var x = 23;

(function(){
  var x = 43;
  
  (function random(){
    x++;
    console.log(x);
    var x = 21;
  })();
})();

Output:

Let's break down the code step by step:

1. `var x = 23;`: This declares a variable `x` in the global scope and initializes it with the value `23`.

2. `(function(){ ... })();`: This is an immediately invoked function expression (IIFE). It creates a new scope. Inside this function:

- `var x = 43;`: This declares a variable `x` within the scope of the outer IIFE and initializes it with the value `43`.

- `(function random(){ ... })();`: Another IIFE is created here. Inside this nested function:

- `x++;`: This increments the value of `x`. Since `x` is declared within the scope of the `random` function but not yet initialized, it will use the value of `x` from the outer scope, which is `43`. So, `x` becomes `44`.

- `console.log(x);`: This logs the value of `x`, which is `44`.

- `var x = 21;`: This declares a new variable `x` within the scope of the `random` function and initializes it with the value `21`. However, due to variable hoisting, this declaration is moved to the top of the function scope, but the assignment is not hoisted. So, `x` remains `44`.

The output will be `44`.

So, even though `x` is declared within the `random` function, the `var x = 21;` declaration is hoisted to the top of the function, but the assignment happens afterward, and it doesn't affect the value of `x` that's being logged.

» JavaScript forEach() vs map() vs filter()

In JavaScript, there are several methods available for iterating over arrays and performing operations on their elements. Three commonly used methods are forEach(), map(), and filter().

forEach(): The forEach() method executes a provided function once for each array element.

Example:

 const numbers = [1, 2, 3, 4, 5];
    numbers.forEach(function(number) {
    console.log(number);
 });

map(): The map() method creates a new array populated with the results of calling a provided function on every element in the calling array.

Example:

const numbers = [1, 2, 3, 4, 5];

// Using map to double each element of the array
const doubledNumbers = numbers.map(function(number) {
  return number * 2;
});

console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]

filter(): The filter() method creates a new array with all elements that pass the test implemented by the provided function.

Example:

const numbers = [1, 2, 3, 4, 5];

// Using filter to get only even numbers from the array
const evenNumbers = numbers.filter(function(number) {
  return number % 2 === 0;
});

console.log(evenNumbers); // Output: [2, 4]

Where to use:

  • Use forEach() for iterating over elements of an array and performing an operation on each element without modifying the original array or creating a new one.
  • Use map() for transforming each element of an array into a new value and creating a new array with the transformed values.
  • Use filter() for creating a new array with elements that satisfy a specific condition or criteria.

» What is singleton design pattern in JavaScript

The Singleton design pattern ensures that a class has only one instance and provides a global point of access to that instance

class SingleSport {
  constructor(sport){
    this.name = sport;

    if(SingleSport.instance){
      return SingleSport.instance;
    }

    SingleSport.instance = this;
  }

  showMsg(){
    console.log("hello", this.name);
  }
}

let obj1 = new SingleSport("cricket");
let obj2 = new SingleSport("Football");

console.log(obj1 === obj2); // Output: true

obj1.showMsg(); // Output: hello cricket
obj2.showMsg(); // Output: hello cricket

In this example, the SingleSport class has a private static property instance to hold the single instance of the class. The constructor checks if an instance already exists; if it does, it returns the existing instance; otherwise, it creates a new instance and assigns it to the instance property. As a result, only one instance of the SingleSport class is created, regardless of how many times it's instantiated.

» Explanation of Pure Functions vs. Impure Functions in JavaScript

Pure Function: A pure function always returns the same result when given the same inputs and has no side effects. It does not modify variables outside its scope or rely on external state. Pure functions are deterministic and have referential transparency.

Example of a Pure Function:

function add(a, b) {
  return a + b;
}

Impure Function: An impure function has side effects or relies on external state. It may modify variables outside its scope, perform I/O operations, or mutate data structures. Impure functions can produce different results for the same inputs and are not referentially transparent.

Example of an Impure Function:

let total = 0;

function addToTotal(amount) {
  total += amount;
}

Key Differences:

  • Pure Functions: Always return the same result for the same inputs, have no side effects, and are referentially transparent.
  • Impure Functions: Can produce different results for the same inputs, have side effects, and may rely on external state.

In summary, understanding the difference between pure and impure functions is crucial for writing clean, predictable, and maintainable JavaScript code. Pure functions promote functional programming principles and make code easier to reason about and test.

» Explain Debounce and Throttle with Examples

Debounce: Debouncing is a technique used to limit the rate at which a function is called. It postpones the execution of a function until after a certain amount of time has passed without the function being called again. This is useful when dealing with events that may trigger the function rapidly, such as scrolling or typing.

Example: Consider a search input field on a webpage. As the user types, an API request is made to fetch search results. Without debouncing, each keystroke would trigger a separate API request, potentially overwhelming the server with unnecessary requests. By debouncing the search function, we can ensure that the API request is only made after the user has finished typing, reducing the number of requests and improving performance.

function debounce(func, delay) {
  let timeoutId;
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

// Example usage
const searchInput = document.getElementById('search-input');
const debounceSearch = debounce(search, 300);

searchInput.addEventListener('input', debounceSearch);

function search() {
  // API call to fetch search results
  console.log('Searching...');
}

Throttle: Throttling is similar to debouncing but limits the rate at which a function is called by ensuring it is not called more than once within a specified interval. Unlike debouncing, throttle guarantees that the function will be executed periodically, at regular intervals, even if the events triggering it continue to occur.

Example: Consider a button on a website that triggers an action when clicked. Without throttling, users may spam-click the button, resulting in the action being executed multiple times rapidly. By throttling the click event handler, we can limit the frequency of function calls, ensuring that the action is executed at a controlled rate, such as once every 500 milliseconds. This prevents excessive execution of the action and provides a smoother user experience.

function throttle(func, delay) {
  let lastExecuted = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastExecuted >= delay) {
      func.apply(this, args);
      lastExecuted = now;
    }
  };
}

// Example usage
const button = document.getElementById('button');
const throttleClick = throttle(handleClick, 1000);

button.addEventListener('click', throttleClick);

function handleClick() {
  // Action to be executed when button is clicked
  console.log('Button clicked!');
}

In summary, debounce and throttle are techniques used to control the rate at which functions are called in response to events. They are valuable tools in optimizing performance and ensuring smooth user interactions in web applications.

» How to flatten an Array and an Object in JavaScript?

Flatten an Array:

function flattenArray(arr) {
  return arr.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenArray(val)) : acc.concat(val), []);
}

// Example usage:
const nestedArray = [1, [2, 3], [4, [5, 6]]];
console.log(flattenArray(nestedArray)); // Output: [1, 2, 3, 4, 5, 6]

This solution recursively flattens a nested array using `reduce()` and `concat()` methods.

Deep Flatten an Object:

function deepFlattenObject(obj) {
  let result = {};

  for (let key in obj) {
    if (typeof obj[key] === 'object' && obj[key] !== null) {
      const flattened = deepFlattenObject(obj[key]);
      for (let subKey in flattened) {
        result[`${key}.${subKey}`] = flattened[subKey];
      }
    } else {
      result[key] = obj[key];
    }
  }

  return result;
}

// Example usage:
const nestedObject = {
  a: 1,
  b: {
    c: 2,
    d: {
      e: 3,
      f: {
        g: 4
      }
    }
  }
};

console.log(deepFlattenObject(nestedObject));
// Output: { "a": 1, "b.c": 2, "b.d.e": 3, "b.d.f.g": 4 }

This solution recursively flattens a nested object by concatenating keys with a dot separator for nested properties. It iterates through each property of the object, checking if the value is an object itself, and then recursively flattens it.

» How to block adding properties to an object

In JavaScript, you can prevent adding properties to an object or making existing properties writable, configurable, or enumerable by using the `Object.freeze()` method or `Object.seal()` method. These methods provide different levels of immutability and control over object properties.

Object.freeze()

- The `Object.freeze()` method freezes an object, preventing new properties from being added to it, and preventing existing properties from being modified or removed.

- This method makes the object immutable, meaning its properties cannot be changed or deleted.

- Example:

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

 Object.freeze(obj);

 // Trying to add a new property
 obj.c = 3; // Throws an error in strict mode, silently fails in non-strict mode

 // Trying to modify an existing property
 obj.a = 10; // Throws an error in strict mode, silently fails in non-strict mode

Object.seal()

- The `Object.seal()` method seals an object, preventing new properties from being added to it, but allowing existing properties to be modified.

- While you cannot add new properties, you can still modify existing properties.

- Example:

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

   Object.seal(obj);

   // Trying to add a new property
   obj.c = 3; // Throws an error in strict mode, silently fails in non-strict mode

   // Modifying an existing property
   obj.a = 10; // Allowed

   // Trying to delete an existing property
   delete obj.b; // Throws an error in strict mode, silently fails in non-strict mode

Both `Object.freeze()` and `Object.seal()` provide a level of immutability and control over object properties. Choose the appropriate method based on whether you need complete immutability (`Object.freeze()`) or the ability to modify existing properties (`Object.seal()`).

» Difference between closures & callback

Closures and callbacks are two fundamental concepts in JavaScript, often used together but serving different purposes. Let's understand each concept and their differences:

1. Closures

- A closure is the combination of a function and the lexical environment within which that function was declared. This lexical environment consists of any variables that were in scope at the time the closure was created.

- Closures allow functions to access and manipulate variables from their parent scope, even after the parent function has finished executing.

- Closures are commonly used to create private variables, maintain state, and create functions with preserved lexical scope.

- Example:

function outer() {
   const message = 'Hello';
   function inner() {
       console.log(message);
   }
   return inner;
}

const closureFunc = outer();
closureFunc(); // Outputs: Hello

2. Callbacks

- A callback is a function passed as an argument to another function, which is then invoked inside the outer function to complete some kind of asynchronous operation or to handle an event.

- Callbacks are commonly used in asynchronous programming, such as handling AJAX requests, event handling, and working with timers.

- Callbacks allow for code to execute asynchronously, meaning that operations can occur out of order and without blocking the main thread of execution.

- Example:

function fetchData(callback) {
     setTimeout(() => {
       const data = 'Some data from the server';
       callback(data);
     }, 1000);
}

function processData(data) {
    console.log('Data received:', data);
}

fetchData(processData);
Differences

- Purpose: Closures are used to maintain scope and state within functions, while callbacks are used to handle asynchronous operations or events.

- Execution: Closures are executed immediately when the parent function is called, while callbacks are executed asynchronously, often after some event or operation completes.

- Syntax: Closures involve nested functions accessing variables from their parent scope, while callbacks involve passing functions as arguments to other functions.

In summary, closures are a mechanism for maintaining scope and state within functions, while callbacks are used for asynchronous programming and event handling in JavaScript. While they are distinct concepts, they are often used together, with closures frequently being created within callback functions to maintain state across asynchronous operations.

» Correct Sequence of Then,catch and Finally chaining method

Promises in JavaScript are used for asynchronous programming, allowing you to handle operations that may take some time to complete, such as fetching data from a server or reading a file. Promises represent a value (either resolved or rejected) that may be available now, or in the future.

Sequence of `then`, `catch`, and `finally` chaining

1. `then`

- The `then` method is used to handle the fulfillment of a promise. It takes two optional callback functions as arguments: one for handling the resolved value of the promise, and one for handling any errors that occur during the execution of the promise.

- The first callback passed to `then` is executed if the promise is resolved successfully.

- The second callback passed to `then` is executed if the promise is rejected.

- The `then` method returns a new promise, allowing for chaining of multiple `then` methods.

2. `catch`

- The `catch` method is used to handle errors that occur in the promise chain. It is similar to passing a rejection handler to the `then` method, but it is more concise and allows for better error handling.

- The `catch` method is typically placed at the end of a promise chain to catch any errors that occur in preceding `then` methods.

- If any error occurs in the preceding promise chain, the control is transferred to the nearest `catch` method.

- The `catch` method also returns a promise, allowing for chaining with other promise methods.

3. `finally`

- The `finally` method is used to execute code after the promise chain has either been fulfilled or rejected.

- The `finally` method is called regardless of whether the promise chain was resolved or rejected, allowing you to perform cleanup actions or other tasks that should always occur, such as closing a database connection or releasing resources.

- The `finally` method does not receive any arguments, and it does not affect the result of the promise chain.

- Unlike `then` and `catch`, `finally` does not return a promise, so you cannot chain additional methods after it.

WISH YOU BEST LUCK :)

1 comment:

  1. Hi wonderful information shared by you:)
    some more ES6 interview question could be find here:

    ReplyDelete