Basic

What are the different data types in JavaScript?

Primitive Types:

Non-Primitive Types:

What is the difference between == and ===?

What is the difference between null and undefined?

What is the difference between let and const?

What is the purpose of typeof in JavaScript?

The typeof operator is used to determine the type of a given variable or expression. It returns a string indicating the type of the operand.

What is NaN in JavaScript?

NaN stands for "Not-a-Number" and represents a value that is not a legal number. It typically occurs when a mathematical operation fails or when trying to convert a non-numeric string to a number.

How do you check if a value is an array in JavaScript?

Use Array.isArray() to check if a value is an array.

const arr = [1, 2, 3];
console.log(Array.isArray(arr));  // true

How do you add or remove elements from an array?

What is the purpose of the this keyword in JavaScript?

The this keyword refers to the object from which the function was called. Its value depends on the context in which the function is called:

const persona = { 
  nombre : 'Alice' , 
  saludo : función () { 
              console.log( ' Hola , ' + this.name ); 
           }   
} 

person.greet(); // "Hola, Alice"

What ES6 features do you use?

Some of the most commonly used ES6 features include let and const, arrow functions, template literals, destructuring, default parameters, rest and spread operators, classes, and modules.

How do you handle errors in JavaScript?

Errors in JavaScript can be handled using try, catch, finally, and throw statements.

try {
  let result = riskyOperation();
} catch (error) {
  console.error('An error occurred:', error.message);
} finally {
  console.log('This code runs regardless of success or error.');
}

function riskyOperation() {
  throw new Error('Something went wrong!');
}

What is the difference between if-else and the ternary operator?

What is hoisting?

Hoisting is JavaScript’s behavior of moving declarations to the top of the current scope (global or function scope). This means variables and function declarations can be used before they are declared.

What are JavaScript built-in methods for string manipulation?

Some common built-in methods for string manipulation include:

charAt() concat() includes() indexOf() lastIndexOf() replace() split() substring() toLowerCase() toUpperCase() trim()

Name some methods you know to work with arrays.

Some array methods include push, pop, shift, unshift, map, filter, reduce, find, forEach, some, every, concat, slice, and splice.

What is the use of the map method? How about find and filter?

What is the difference between slice() and splice() in arrays?

What is the difference between map and reduce?

map creates a new array with the results of calling a provided function on every element in the calling array.

reduce executes a reducer function on each element of the array, resulting in a single output value.

Explain the difference between for, for...of, and for...in loops.

What is the use of callback functions?

Callback functions are used to handle asynchronous operations. They are passed as arguments to other functions and are invoked after an operation is completed, allowing you to execute code in response to the outcome of the asynchronous operation.

What are arrow functions?

Arrow functions are a concise way to write function expressions in JavaScript. They use the => syntax and do not have their own this context, which makes them useful in situations where you want to preserve the context of this from the enclosing scope.

What is the difference between promises and callbacks?

How can you handle errors when fetching data?

You can handle errors when fetching data by using try...catch blocks or handling the promise rejections using .catch() method.

What is an IIFE (Immediately Invoked Function Expression)?

An IIFE is a function that is executed immediately after its definition. It is commonly used to create a local scope and avoid polluting the global namespace.

( función () { 
  const message = 'IIFE ejecutado' ; 
  console . log (mensaje); 
})(); // "IIFE ejecutado"

What are promises?

Promises are objects representing the eventual completion or failure of an asynchronous operation. They provide a way to handle asynchronous operations more gracefully by allowing you to chain .then() and .catch() methods to handle successful outcomes and errors, respectively.

What is the difference between setTimeout and setInterval?

Do you know the spread operator? What is its use?

The spread operator (...) allows an iterable such as an array or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.

Where do you usually use the rest operator?

The rest operator (`…`) is used in function parameters to collect all remaining arguments into an array, and in array and object destructuring to collect the rest of the elements/properties.

What are template literals, and how do you use them?

Template literals are string literals enclosed in backticks (`) that allow for multi-line strings and interpolation of expressions.

const name = 'John';
const greeting = `Hello, ${name}!`;
console.log(greeting); // Output: Hello, John!

What is the purpose of JSON.stringify() and JSON.parse()?

Can you explain the Observer pattern in JavaScript and provide a real-world use case where it would be useful?

The Observer pattern is a design pattern where an object (called the subject) maintains a list of observers that are notified of any state changes. When the subject's state changes, all registered observers automatically get updated.

Real-world use case: In JavaScript, the Observer pattern is useful in scenarios like event handling. For example, in a chat application, when a new message arrives (subject changes), all the chat windows (observers) need to update with the new message.

class Subject {
  constructor() {
    this.observers = [];
  }
  addObserver(observer) {
    this.observers.push(observer);
  }
  notifyObservers(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

class Observer {
  update(data) {
    console.log(`Received update: ${data}`);
  }
}

const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();

subject.addObserver(observer1);
subject.addObserver(observer2);
subject.notifyObservers("New message"); // Both observers get the update

What are some common design patterns used in JavaScript?

How would you implement the Singleton pattern in JavaScript, and when is it appropriate to use it?

The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. It's appropriate when you need to maintain a single global instance of a class (e.g., a database connection or a configuration manager).

class Singleton {
  constructor() {
    if (Singleton.instance) {
      return Singleton.instance;
    }
    Singleton.instance = this;
    this.data = "Singleton instance";
  }
  
  getData() {
    return this.data;
  }
}

const instance1 = new Singleton();
const instance2 = new Singleton();

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

Use cases: Logging, configuration management, or managing a single instance of database connections.

What is data streaming in JavaScript, and how does it differ from traditional data handling methods?

Data streaming refers to continuously receiving or sending data in small chunks, rather than waiting for the entire dataset to be available. This is especially useful for handling large data (like video, audio, or file downloads).

Difference from traditional methods:

In JavaScript, streaming is often implemented using Streams API (ReadableStream, WritableStream).

What is the difference between unit testing, integration testing, and end-to-end testing in JavaScript?

How would you mock API calls in unit tests when testing components that rely on external data?

To mock API calls in unit tests, you can use libraries like Jest with jest.mock() or axios-mock-adapter to simulate HTTP responses, ensuring your tests remain independent from actual API behavior.

import axios from 'axios';
import MyComponent from './MyComponent';
import { render, screen } from '@testing-library/react';

jest.mock('axios');

test('fetches data and renders component', async () => {
  axios.get.mockResolvedValue({ data: { name: 'John' } });

  render(<MyComponent />);
  
  const element = await screen.findByText('John');
  expect(element).toBeInTheDocument();
});

What are some best practices for writing tests with Jest and React Testing Library?

Write a function to print “fizz” if a number is divisible by 3, “buzz” if the number is divisible by 5, and “fizzbuzz” if the number is divisible by both 3 and 5.

const fun = () => {
    for (let i = 1; i <= 50; i++) {
      if (i % 3 === 0 && i % 5 === 0) {
        console.log(i, 'FIZBUZZ');
      } else if (i % 5 === 0) {
        console.log(i, 'BUZZ');
      } else if (i % 3 === 0) {
        console.log(i, 'FIZ');
      }
    }
  };

Reverse a string.

function reverseString(str) {
    return str.split('').reverse().join('');
}

function reverseString(str) {
  let reversed = '';
  for (let i = str.length - 1; i >= 0; i--) {
    reversed += str[i];
  }
  return reversed;
}

Find the Largest Number in an Array

findLargest(arr) {
  return Math.max(...arr);
}
console.log(findLargest([1, 3, 7, 2, 9])); // Output: 9

Check for Palindrome

function isPalindrome(str) {
  return str === str.split('').reverse().join('');
}
console.log(isPalindrome("racecar")); // Output: true

Write a function to calculate the factorial of a number.

function factorial(n) {
    if (n === 0 || n === 1) {
        return 1;
    } else {
        return n * factorial(n - 1);
    }
}

Return the longest word in a string.

function longestWord(str) {
    const words = str.split(' ');
    let longest = '';
    
    for (let word of words) {
        if (word.length > longest.length) {
            longest = word;
        }
    }
    
return longest;
}

What will be the output?

let a={name: 'Suman', age:22}
let b={name: 'Suman', age:22}

console.log(a === b)
// false 
console.log(a)
console.log(b)

var a = b = 5

// undefined
// ReferenceError: b is not defined
for(var i = 0; i<5; i++) {
  setTimeout(() =>{
    console.log(i)
  },1000)
}

// 5 5 5 5 5

for(let i = 0; i<5; i++) {
  setTimeout(() =>{
    console.log(i)
  },1000)
}
// 0 1 2 3 4
console.log('6' + 5 * 6)
console.log('6' * 5 + 6)
console.log('5' - '3' + 6)

// 630
// 36
// 8
isNAN("Hello")
// true
function fun1 ( ){
    setTimeout(() => {
        console.log(x)
        console.log(y)
    },3000)
    
    var x = 2
    let y = 12
}

fun1()
// 2
// 12
var a = 5
console.log(a++)
console.log(a)
// 5
// 6

console.log(++a)
console.log(a)
// 6
// 6
console.log(1<2<3)
console.log(3>2>1)
// true
// false


Intermediate

Explain the difference between synchronous and asynchronous code.

What is the purpose of the Promise.all() method?

The Promise.all() method is used to run multiple promises concurrently and wait until all of them have resolved or at least one has rejected. It takes an iterable (like an array) of promises as an input and returns a single promise that:

This method is useful when you need to perform multiple asynchronous operations concurrently and want to proceed only when all of them are completed.

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3])
  .then((values) => {
    console.log(values); // [3, 42, "foo"]
  })
  .catch((error) => {
    console.error(error);
  });

Explain the difference between Promise.all() and Promise.race() .

What is the difference between function declaration and function expression?

How does JavaScript handle scope and what is lexical scoping?

What is currying in JavaScript and how does it work?

Currying is a technique where a function is transformed into a series of nested functions that take one argument at a time.

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

const add5 = add(5);
console.log(add5(3));  // 8

Currying allows partial application of functions and can be useful in functional programming.

How do you debounce or throttle a function in JavaScript?

What is the difference between synchronous and asynchronous iteration in JavaScript?

What is the Temporal Dead Zone in JavaScript?

The Temporal Dead Zone (TDZ) refers to the period between the start of a block and the moment a variable is declared. Accessing the variable during this time results in a ReferenceError.

It occurs with variables declared using let and const.

{
  console.log(a); // ReferenceError: Cannot access 'a' before initialization
  let a = 5;
}

How does destructuring assignment work for arrays and objects?

Destructuring allows you to unpack values from arrays or properties from objects into distinct variables.

You can also provide default values, rename variables, and extract nested properties.

Explain what Object.freeze() and Object.seal() do.

What is the difference between deep copy and shallow copy in JavaScript?

What is a Symbol in JavaScript, and why would you use it?

A Symbol is a unique, immutable primitive value used to create property keys that won’t collide with any other keys (even those with the same name).

Symbols are often used in JavaScript frameworks or libraries, such as iterators (e.g., Symbol.iterator).

What are events in JavaScript?

Events in JavaScript are actions or occurrences that happen in the system you are programming, which the system tells you about so you can respond to them. They are used to handle user interactions and other activities that occur in a web page, such as clicking a button, hovering over an element, or submitting a form.

Common event types include:

What is the difference between call(), apply(), and bind()?

call(), apply(), and bind() are methods available on JavaScript functions that allow you to control the this value and pass arguments to the function.

The call() method calls a function with a given this value and arguments provided individually.

function greet(greeting, punctuation) {
  console.log(greeting + ', ' + this.name + punctuation);
}

const person = { name: 'Alice' };

greet.call(person, 'Hello', '!'); // Hello, Alice!

apply() method calls a function with a given this value and arguments provided as an array (or an array-like object).

función saludar ( saludo, puntuación ) { 
  console.log (saludo + ', ' + this.nombre + puntuación); 
}
 
const persona = { nombre : 'Bob' }; 

saludar.apply (persona, [ ' Hola' , ' .' ] ); // Hola, Bob.

The bind() method creates a new function that, when called, has its this value set to the value provided, with a given sequence of arguments preceding any value provided when the new function is called. Unlike call() and apply(), bind() does not execute the function immediately it returns a new function.

función  saludar ( saludo , puntuación ) { 
  console.log (saludo + ', ' + this.nombre + puntuación ); 
} 

const persona = { nombre : 'Charlie' }; 

const personaSaludo = saludar.bind ( persona, 'Hola' , '?' ); 

personaSaludo (); // Se puede llamar más tarde

What is the event loop?

The event loop is a mechanism that allows JavaScript to perform non-blocking I/O operations despite being single-threaded. It continuously checks the call stack and the task queue, executing functions from the task queue when the call stack is empty.

In the event loop, what is executed first, a promise or a setTimeout?

Promises are executed before setTimeout. When both a promise and a setTimeout are scheduled to run at the same time, the .then() callback of the promise will execute first because promises are part of the microtask queue, which has higher priority than the macrotask queue where setTimeout resides.

Why is JavaScript single-threaded?

JavaScript is single-threaded to simplify the execution model and avoid concurrency issues like race conditions. It uses an event loop to manage asynchronous operations, allowing it to perform non-blocking I/O operations despite being single-threaded.

Explain the DOMContentLoaded event.

The DOMContentLoaded event fires when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading. This event is useful for executing JavaScript code as soon as the DOM is ready.

Can you explain memoization?

Memoization is an optimization technique that stores the results of expensive function calls and returns the cached result when the same inputs are provided again. It helps improve the performance of functions by avoiding redundant calculations.

What are JavaScript modules, and how do you implement them?

JavaScript modules allow you to break up your code into smaller, reusable chunks, which can be imported and exported between files. Modules help maintain modularity and encapsulation, making code more organized and manageable.

Modern JavaScript (ES6+) uses import and export for module implementation. You can also use CommonJS (Node.js modules) with require and module.exports.

Can you explain the concept of immutability and how it can be achieved?

Immutability refers to the inability to change an object after it has been created. In JavaScript, immutability can be achieved by using methods that do not modify the original object but instead return a new object with the desired changes. Examples include Object.freeze() for objects and methods like concat(), slice(), and map() for arrays.

Are arrow functions hoisted?

No, arrow functions are not hoisted. They behave like function expressions, meaning they are not available before their declaration

Explain the concept of closures and provide an example

A closure is a feature in JavaScript where an inner function has access to the outer (enclosing) function’s variables, even after the outer function has finished executing. This allows the inner function to “remember” the environment in which it was created.

function outerFunction() {
    let outerVariable = "I am outside!";

    function innerFunction() {
        console.log(outerVariable);
    }

    return innerFunction;
}

const myClosure = outerFunction();
myClosure(); // Output: "I am outside!"

What is event delegation?

Event delegation is a powerful pattern in web development that allows you to manage events more efficiently by taking advantage of the event bubbling (or propagation) feature in the DOM. Instead of attaching an event listener to each individual element, event delegation involves attaching a single event listener to a parent element. This listener can then detect events triggered by any of its child elements.

How do you implement lazy loading in JavaScript?

What are higher-order functions?

A higher-order function is a function that either:

  1. Takes one or more functions as arguments.
  2. Returns a function as its result.

Higher-order functions are commonly used in JavaScript for functional programming techniques like callbacks, map, filter, or reduce.

function higherOrder(fn) {
  return function() {
    return fn();
  };
}

function sayHello() {
  return 'Hello!';
}

const greet = higherOrder(sayHello);
console.log(greet());  // Output: 'Hello!'

What is a prototype in JavaScript, and how does inheritance work?

In JavaScript, every object has a hidden property called [[Prototype]], which refers to another object, known as its prototype. Prototype inheritance allows objects to inherit properties and methods from their prototype chain.

Inheritance works by delegating property or method lookups to the prototype object if they are not found on the instance itself.

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

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

const person1 = new Person('John');
person1.sayHello();  // Output: 'Hello, my name is John'

In this example, person1 inherits the sayHello method from Person.prototype.

Explain the concept of debouncing and throttling, and how they can be implemented.

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

const debouncedFunction = debounce(() => console.log('Debounced!'), 300);

function throttle(func, limit) {
  let lastFunc;
  let lastRan;
  return function(...args) {
    if (!lastRan) {
      func.apply(this, args);
      lastRan = Date.now();
    } else {
      clearTimeout(lastFunc);
      lastFunc = setTimeout(() => {
        if (Date.now() - lastRan >= limit) {
          func.apply(this, args);
          lastRan = Date.now();
        }
      }, limit - (Date.now() - lastRan));
    }
  };
}

const throttledFunction = throttle(() => console.log('Throttled!'), 300);

Explain the concept of Dependency Injection. How would you implement it in a JavaScript application?

Dependency Injection (DI) is a design pattern where an object’s dependencies are injected rather than being hard-coded within the object. This promotes flexibility, reusability, and testing.

class Logger {
  log(message) {
    console.log(message);
  }
}

class UserService {
  constructor(logger) {
    this.logger = logger;
  }

  createUser(user) {
    this.logger.log(`User created: ${user.name}`);
  }
}

// Inject the dependency (logger) into UserService
const logger = new Logger();
const userService = new UserService(logger);

userService.createUser({ name: 'John' });

In this example, the UserService class depends on the Logger class, but instead of creating a Logger object inside UserService, it's passed in.

How would you implement real-time data streaming in a web application?

Dependency Injection (DI) is a design pattern where an object’s dependencies are injected rather than being hard-coded within the object. This promotes flexibility, reusability, and testing.

class Logger {
  log(message) {
    console.log(message);
  }
}

class UserService {
  constructor(logger) {
    this.logger = logger;
  }

  createUser(user) {
    this.logger.log(`User created: ${user.name}`);
  }
}

// Inject the dependency (logger) into UserService
const logger = new Logger();
const userService = new UserService(logger);

userService.createUser({ name: 'John' });

In this example, the UserService class depends on the Logger class, but instead of creating a Logger object inside UserService, it's passed in.

Explain how the ReadableStream and WritableStream APIs work in JavaScript.

These APIs are commonly used in handling files, network responses, or large data processing in small chunks.

Can you explain the Observer pattern and provide a use case where it would be beneficial in a JavaScript application?

The Observer Pattern is a behavioral design pattern where an object (called the subject) maintains a list of dependents (observers) and notifies them of any state changes.

Use Case: The Observer Pattern is useful in event-driven architectures, like user interface updates or real-time data feeds.

class Subject {
  constructor() {
    this.observers = [];
  }

  subscribe(observer) {
    this.observers.push(observer);
  }

  unsubscribe(observer) {
    this.observers = this.observers.filter(obs => obs !== observer);
  }

  notify(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

class Observer {
  update(data) {
    console.log('Observer received:', data);
  }
}

// Usage
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();

subject.subscribe(observer1);
subject.subscribe(observer2);

subject.notify('New data available!');  // Both observers will be notified

In a real-world scenario, you could use the Observer Pattern for notifications in a chat app or real-time updates in a collaborative document editing tool.

Can you explain the Factory pattern and provide a use case where it would be beneficial in a JavaScript application?

The Factory Pattern is a creational design pattern that provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created. Instead of directly instantiating objects, you delegate this responsibility to a separate factory function or class, which decides what specific instance to create.

Use Case: The Factory Pattern is useful when you have multiple subclasses of an object or need dynamic object creation based on certain conditions.

class Car {
  constructor(model) {
    this.model = model;
  }
}

class Bike {
  constructor(model) {
    this.model = model;
  }
}

class VehicleFactory {
  static createVehicle(type, model) {
    if (type === 'car') {
      return new Car(model);
    } else if (type === 'bike') {
      return new Bike(model);
    }
  }
}

// Usage
const car = VehicleFactory.createVehicle('car', 'Sedan');
const bike = VehicleFactory.createVehicle('bike', 'Mountain Bike');

console.log(car instanceof Car);  // true
console.log(bike instanceof Bike);  // true

Benefit: The Factory Pattern is beneficial in scenarios where the exact type of object needs to be determined dynamically at runtime, such as in a vehicle or game entity creation system.

Can you explain the Strategy pattern and provide a use case where it would be beneficial in a JavaScript application?

The Strategy Pattern is a behavioral design pattern that defines a family of algorithms and makes them interchangeable. The strategy pattern lets the algorithm vary independently from the clients that use it. It is often used when you have multiple ways to achieve a task, and you want to switch between them at runtime.

Use Case: The Strategy Pattern is useful in scenarios where you want to swap between different business rules, such as different payment methods or sorting algorithms.

class PayPal {
  pay(amount) {
    console.log(`Paid ${amount} using PayPal`);
  }
}

class CreditCard {
  pay(amount) {
    console.log(`Paid ${amount} using Credit Card`);
  }
}

class PaymentContext {
  constructor(strategy) {
    this.strategy = strategy;
  }

  setStrategy(strategy) {
    this.strategy = strategy;
  }

  executeStrategy(amount) {
    this.strategy.pay(amount);
  }
}

// Usage
const payment = new PaymentContext(new PayPal());
payment.executeStrategy(100);  // Output: Paid 100 using PayPal

payment.setStrategy(new CreditCard());
payment.executeStrategy(200);  // Output: Paid 200 using Credit Card

Benefit: The Strategy Pattern is beneficial when you need to switch between multiple algorithms or behaviors at runtime, making it great for payment processing or data sorting.

How would you test asynchronous code in JavaScript, especially promises and async/await functions?

Testing asynchronous code in JavaScript can be done using Jest or similar testing frameworks. For promises and async/await, you can use the following approaches:

Example of Promise Test (Using async/await):

jsCopy code// Function to test
const fetchData = () => {
  return new Promise(resolve => setTimeout(() => resolve('data'), 100));
};

// Jest Test
test('fetches data asynchronously', async () => {
  const data = await fetchData();
  expect(data).toBe('data');
});

Example of Promise Test (Using .resolves):

jsCopy codetest('fetches data resolves', () => {
  return expect(fetchData()).resolves.toBe('data');
});

Testing Error Handling in Promises:

jsCopy codeconst fetchWithError = () => Promise.reject('Error occurred');

test('fetch fails with an error', () => {
  return expect(fetchWithError()).rejects.toBe('Error occurred');
});

Benefits: Jest automatically handles async code, and these techniques ensure that tests handle promises and async/await cleanly, providing flexibility for testing asynchronous operations, including API calls and timers.

How does snapshot testing work in Jest, and what are the advantages and limitations of using it?

Snapshot Testing in Jest is a way to ensure that a component's output (usually its rendered UI) matches a previously saved snapshot of the component. If the output differs, Jest will flag the test as failed, which may indicate that the UI has changed unintentionally.

How Snapshot Testing Works:

  1. When you run a snapshot test for the first time, Jest saves the component's output in a snapshot file.
  2. On subsequent runs, Jest compares the new output with the saved snapshot.
  3. If the output has changed, Jest will either fail the test or allow you to update the snapshot.

import React from 'react';
import renderer from 'react-test-renderer';
import MyComponent from './MyComponent';

test('renders correctly', () => {
  const tree = renderer.create(<MyComponent />).toJSON();
  expect(tree).toMatchSnapshot();
});

Snapshot testing is best for UI regression testing but should be combined with other testing methods (unit, integration) for full test coverage.

Explain how you would test components that make use of the Context API or Redux for global state management.

1. Testing Components with Context API

When testing React components that rely on the Context API for global state, you need to simulate the context value by wrapping the component in its corresponding Provider during testing.

import React from 'react';
import { render, screen } from '@testing-library/react';
import UserContext from './UserContext'; // Your context
import UserComponent from './UserComponent'; // Component that uses the context

test('renders user name from context', () => {
  const mockUser = { name: 'John Doe' };
  
  // Render the component wrapped in the context provider
  render(
    <UserContext.Provider value={mockUser}>
      <UserComponent />
    </UserContext.Provider>
  );

  // Assert that the component correctly displays the context value
  expect(screen.getByText(/John Doe/i)).toBeInTheDocument();
});

2. Testing Components with Redux

For Redux, testing involves wrapping the component with the Redux Provider and passing a mock store to it. This allows you to simulate Redux state and actions during tests.

import React from 'react';
import { render, screen } from '@testing-library/react';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import MyComponent from './MyComponent'; // Component connected to Redux

const mockStore = configureStore([]);

test('renders value from Redux state', () => {
  const store = mockStore({
    myState: { value: 'Hello World' }, // Mock Redux state
  });

  // Render component wrapped in the Redux provider
  render(
    <Provider store={store}>
      <MyComponent />
    </Provider>
  );

  // Assert that the component correctly renders data from the mock state
  expect(screen.getByText(/Hello World/i)).toBeInTheDocument();
});

Key Considerations:

Implement a custom observer pattern in JavaScript

  1. Subject: The subject is responsible for keeping track of the observers and notifying them when a change happens.
  2. Observer: Observers subscribe to the subject, and when the subject changes, the observers are notified.
// Subject Class
class Subject {
  constructor() {
    this.observers = []; // List to store observers (subscribers)
  }

  // Subscribe an observer
  subscribe(observer) {
    this.observers.push(observer);
  }

  // Unsubscribe an observer
  unsubscribe(observer) {
    this.observers = this.observers.filter(sub => sub !== observer);
  }

  // Notify all observers
  notify(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

// Observer Class
class Observer {
  constructor(name) {
    this.name = name;
  }

  // Update method called by the subject when changes occur
  update(data) {
    console.log(`${this.name} received update:`, data);
  }
}

// Example usage:

// Create a subject (e.g., weather station)
const weatherStation = new Subject();

// Create observers (e.g., weather displays)
const display1 = new Observer('Display 1');
const display2 = new Observer('Display 2');

// Subscribe displays to the weather station
weatherStation.subscribe(display1);
weatherStation.subscribe(display2);

// Notify observers with data (e.g., temperature update)
weatherStation.notify('Temperature is 25°C');

// Unsubscribe one observer
weatherStation.unsubscribe(display1);

// Notify again
weatherStation.notify('Temperature is 28°C');

Explanation:

  1. Subject Class:
    • It maintains a list of observers and has methods to add (subscribe), remove (unsubscribe), and notify (notify) observers.
    • When the subject’s state changes, it calls notify() to inform all observers.
  2. Observer Class:
    • It represents the subscribers who are interested in being updated when the subject changes.
    • The update() method is called when the subject notifies them, and it can handle the received data.

Example Output:

Display 1 received update: Temperature is 25°C
Display 2 received update: Temperature is 25°C
Display 2 received update: Temperature is 28°C

Use Case:

Flatten a Nested Array

function flattenArray(arr) {
  for (let i = 0; i < arr.length; i++) {
      const value = arr[i];
      if (Array.isArray(value)) {
        // If the value is an array, recursively call the function flatten
        result = result.concat(flatten(value));
      } else {
        // If it is not an array, simply add the value to the result
        result.push(value);
      }
  }

  return result;
};

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


const arr = [1, [2, [3, [4]], 5]];
const flattened = flattenArray(arr);
console.log(flattened); // [1, 2, 3, 4, 5]

// using flat()
const arr1 = [1, [2, [3, [4]], 5]];
const flatArr1 = arr1.flat(Infinity);
console.log(flatArr1); // [1, 2, 3, 4, 5]

function  memoize ( fn ) {
     const  cache = {};
     return  function ( ...args ) {
         const  key = JSON.stringify ( args);
         if (cache[key]) {
             return cache[key];
        } else {
             const  result = fn ( ...args ) ;
            cache[key] = result;
             return result;
        }
    };
}


Advanced

What is the Temporal Dead Zone, and how does it relate to variable declarations in JavaScript?

Explain what memoization is and how it can improve performance in JavaScript.

Memoization is a programming technique used to improve the performance of functions by caching the results of expensive function calls and returning the cached result when the same inputs occur again.

What is a Proxy object in JavaScript and how can you use it?

A Proxy object in JavaScript allows you to intercept and customize operations performed on an object, such as property access, assignment, function invocation, etc.

const target = {
  name: 'John',
  age: 25
};

const handler = {
  get: (obj, prop) => {
    return prop in obj ? obj[prop] : `Property "${prop}" does not exist`;
  },
  set: (obj, prop, value) => {
    if (prop === 'age' && typeof value !== 'number') {
      throw new TypeError('Age must be a number');
    }
    obj[prop] = value;
    return true;
  }
};

const proxy = new Proxy(target, handler);

// Intercepted property access
console.log(proxy.name);  // John
console.log(proxy.height);  // Property "height" does not exist

// Intercepted property assignment
proxy.age = 30;
console.log(proxy.age);  // 30

// Error when setting invalid type
proxy.age = 'thirty';  // Throws TypeError

Use Cases:

What are the performance implications of using large numbers of event listeners in JavaScript?

Using a large number of event listeners can have a negative impact on the performance of a web application, especially when attached to many elements or when handling complex events.

Performance Implications:

Example of Event Delegation:

// Instead of attaching listeners to each button, we use one listener on the parent
document.getElementById('parent').addEventListener('click', (event) => {
  if (event.target.tagName === 'BUTTON') {
    console.log('Button clicked:', event.target.textContent);
  }
});

How do WebAssembly and JavaScript interact, and why might you use them together?

WebAssembly (Wasm) is a low-level binary instruction format designed to run in web browsers alongside JavaScript. It allows developers to write code in languages like C, C++, or Rust and compile it to WebAssembly for use on the web.

Interaction Between WebAssembly and JavaScript:

Why Use WebAssembly with JavaScript:

Explain the concept of event bubbling and event capturing.

When an event is triggered on a DOM element, there are two main phases through which the event travels: event capturing and event bubbling.

Event Bubbling:

Event Capturing (also known as Event Trickling):

Event Propagation Flow:

  1. Capture Phase: The event travels from the root to the target element.
  2. Target Phase: The event is processed at the target element.
  3. Bubbling Phase: The event bubbles up from the target back to the root, triggering parent listeners.

Use Case Example:

<div id="parent">
  <button id="child">Click Me</button>
</div>

<script>
  document.getElementById('parent').addEventListener('click', () => {
    console.log('Parent clicked');
  });

  document.getElementById('child').addEventListener('click', () => {
    console.log('Child clicked');
  });
</script>

Capturing Example:

document.getElementById('parent').addEventListener('click', () => {
  console.log('Parent clicked');
}, true);  // Capture mode

document.getElementById('child').addEventListener('click', () => {
  console.log('Child clicked');
});

Preventing Bubbling:

You can prevent an event from propagating up the DOM by using event.stopPropagation().

document.getElementById('child').addEventListener('click', (event) => {
  event.stopPropagation();  // Stops the event from bubbling up
  console.log('Child clicked');
});

How does JavaScript handle memory management and garbage collection?

JavaScript uses automatic memory management and relies on a process called garbage collection to reclaim memory that is no longer in use.

Key Concepts:

What are generators and how do you use them?

Generators are a special type of function in JavaScript that can be paused and resumed, allowing for the creation of iterators in a more straightforward manner.

Key Features:

function* myGenerator() {
  yield 1;
  yield 2;
  yield 3;
}
  1. Creating an Iterator:
const iterator = myGenerator();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
  1. Use Case: Generators are useful for implementing iterators, managing asynchronous code with async/await, and creating sequences.

What are WeakMap and WeakSet? How are they different from Map and Set?

WeakMap and WeakSet are collections in JavaScript that hold "weak" references to objects, meaning that they do not prevent garbage collection if there are no other references to the objects.

WeakMap:

let obj = {};
let weakMap = new WeakMap();
weakMap.set(obj, 'value');
console.log(weakMap.get(obj)); // 'value'
obj = null; // The object can be garbage collected

WeakSet:

let obj = {};
let weakSet = new WeakSet();
weakSet.add(obj);
console.log(weakSet.has(obj)); // true
obj = null; // The object can be garbage collected

Key Differences:

What is the difference between the stack and the heap in JavaScript memory management?

What are service workers, and how can they be used in JavaScript?

Service Workers are scripts that run in the background in the browser, independent of the web page, enabling features like offline support, push notifications, and background sync.

Key Features:

Usage:

  1. Registering a Service Worker:
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js').then((registration) => {
    console.log('Service Worker registered:', registration);
  }).catch((error) => {
    console.log('Registration failed:', error);
  });
}
  1. In sw.js (Service Worker File):
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('static-cache').then((cache) => {
      return cache.addAll(['/index.html', '/styles.css', '/app.js']);
    })
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request);
    })
  );
});
  1. Use Case: Service workers are commonly used to improve the performance and reliability of web apps by allowing them to work offline and load faster using cached content.

What is the Decorator pattern, and how can it be implemented in JavaScript to extend object behavior?

The Decorator pattern is a structural design pattern that allows behavior to be added to individual objects, dynamically, without affecting the behavior of other objects from the same class. It provides a flexible alternative to subclassing for extending functionality.

  1. Basic Object:

    class Car {
      getDescription() {
        return "Basic car";
      }
      cost() {
        return 10000;
      }
    }
    
    

  2. Decorator Class:

    class CarDecorator {
      constructor(car) {
        this.car = car;
      }
      getDescription() {
        return this.car.getDescription();
      }
      cost() {
        return this.car.cost();
      }
    }
    
    class SportsPackage extends CarDecorator {
      getDescription() {
        return this.car.getDescription() + ', Sports Package';
      }
      cost() {
        return this.car.cost() + 5000;
      }
    }
    
    class Sunroof extends CarDecorator {
      getDescription() {
        return this.car.getDescription() + ', Sunroof';
      }
      cost() {
        return this.car.cost() + 1500;
      }
    }
    
    

  3. Using the Decorators:

    let myCar = new Car();
    myCar = new SportsPackage(myCar);
    myCar = new Sunroof(myCar);
    
    console.log(myCar.getDescription()); // "Basic car, Sports Package, Sunroof"
    console.log(myCar.cost()); // 16500
    
    

  4. Use Case: This pattern is beneficial when you need to add responsibilities to objects dynamically. For example, in a UI framework, decorators can be used to add extra functionality (like adding borders or shadows) to components without altering their base implementation.

Explain how JavaScript can be used for real-time data streaming and which APIs or techniques are used.

What is test-driven development (TDD), and how would you apply it to a JavaScript project?

Test-Driven Development (TDD) is a software development approach where tests are written before the actual code. The development process follows a Red-Green-Refactor cycle:

  1. Red: Write a failing test (because the feature does not exist yet).
  2. Green: Write just enough code to make the test pass.
  3. Refactor: Improve the code quality without changing its behavior, ensuring the test still passes.

TDD Process:

  1. Write a Test:

    • Write a test case that defines a specific functionality.
    // sum.test.js
    const sum = require('./sum');
    
    test('adds 2 + 3 to equal 5', () => {
      expect(sum(2, 3)).toBe(5);  // Test will fail because sum function doesn't exist yet.
    });
    
    

  2. Write the Minimum Code:

    • Implement the function that satisfies the test.
    // sum.js
    function sum(a, b) {
      return a + b;
    }
    module.exports = sum;
    
    

  3. Refactor:

    • After the test passes, clean up or optimize the code while ensuring it still meets the test requirements.

  4. Repeat the Cycle:

    • Write another test, make it pass, refactor, and continue.

Benefits:

Example Use Case:

Write a function that performs a deep clone of a given object.

function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  
  if (Array.isArray(obj)) {
    return obj.map(item => deepClone(item));
  }
  
  const clone = {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key]);
    }
  }
  
  return clone;
}

// Example:
const obj = { a: 1, b: { c: 2, d: [3, 4] }};
const clonedObj = deepClone(obj);
console.log(clonedObj);

structuredClone() is a built-in function in modern JavaScript (introduced in ECMAScript 2021) that creates a deep clone of a given value. It supports cloning of many built-in types, including objects, arrays, maps, sets, dates, and more, without many of the limitations.

const original = {
  a: 1,
  b: {
    c: 2,
    d: [3, 4, 5]
  },
  e: new Date(),
  f: new Map([['key', 'value']])
};

const deepCopy = structuredClone(original);

console.log(deepCopy); // { a: 1, b: { c: 2, d: [3, 4, 5] }, e: Date, f: Map }
console.log(deepCopy !== original); // true
console.log(deepCopy.b !== original.b); // true
console.log(deepCopy.b.d !== original.b.d); // true
console.log(deepCopy.e !== original.e); // true
console.log(deepCopy.f !== original.f); // true

// Modifying the copy does not affect the original
deepCopy.b.c = 10;
deepCopy.b.d.push(6);
deepCopy.e.setFullYear(2000);
deepCopy.f.set('key', 'newValue');

console.log(original.b.c); // 2
console.log(original.b.d); // [3, 4, 5]
console.log(original.e.getFullYear()); // Current year
console.log(original.f.get('key')); // "value"

Write a function to compares objects.

Shallow comparison involves comparing the objects’ own properties and values but not nested objects.

function shallowEqual(obj1, obj2) {
  if (obj1 === obj2) return true;
  
  if (typeof obj1 !== 'object' || obj1 === null || 
      typeof obj2 !== 'object' || obj2 === null) {
    return false;
  }
  
  const keys1 = Object.keys(obj1);
  const keys2 = Object.keys(obj2);
  
  if (keys1.length !== keys2.length) return false;
  
  for (let key of keys1) {
    if (obj1[key] !== obj2[key]) {
      return false;
    }
  }
  
  return true;
}
// Example usage:
const obj1 = { a: 1, b: 2 };
const obj2 = { a: 1, b: 2 };
const obj3 = { a: 1, b: 3 };
console.log(shallowEqual(obj1, obj2)); // true
console.log(shallowEqual(obj1, obj3)); // false

Deep comparison involves recursively comparing all nested objects and arrays.

function deepEqual(obj1, obj2) {
  if (obj1 === obj2) return true;
  
  if (typeof obj1 !== 'object' || obj1 === null || 
      typeof obj2 !== 'object' || obj2 === null) {
    return false;
  }
  const keys1 = Object.keys(obj1);
  const keys2 = Object.keys(obj2);
  
  if (keys1.length !== keys2.length) return false;
  
  for (let key of keys1) {
    if (!keys2.includes(key)) return false;
    if (!deepEqual(obj1[key], obj2[key])) return false;
  }
  return true;
}
// Example usage:
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { a: 1, b: { c: 2 } };
const obj3 = { a: 1, b: { c: 3 } };
console.log(deepEqual(obj1, obj2)); // true
console.log(deepEqual(obj1, obj3)); // false

Implement a memoize function,

function memoize(fn) {
  const cache = {};
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache[key]) {
      return cache[key];
    }
    const result = fn(...args);
    cache[key] = result;
    return result;
  };
}

function slowFunction(num) {
  console.log("Computing...");
  return num * 2;
}

const memoizedFn = memoize(slowFunction);
console.log(memoizedFn(5)); // Output: "Computing..." 10
console.log(memoizedFn(5)); // Output: 10 (cached result)

Implement the Observer pattern (publish/subscribe model) in JavaScript.

class Subject {
  constructor() {
    this.observers = [];
  }

  subscribe(observer) {
    this.observers.push(observer);
  }

  unsubscribe(observer) {
    this.observers = this.observers.filter(sub => sub !== observer);
  }

  notify(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

class Observer {
  update(data) {
    console.log('Observer received:', data);
  }
}

const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();

subject.subscribe(observer1);
subject.subscribe(observer2);

subject.notify('Event 1');
subject.unsubscribe(observer2);
subject.notify('Event 2');

Implement a custom version of Promise.all() that resolves when all promises have resolved or rejects if any promise rejects.

function customPromiseAll(promises) {
  return new Promise((resolve, reject) => {
    const results = [];
    let completed = 0;

    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then(result => {
          results[index] = result;
          completed++;

          if (completed === promises.length) {
            resolve(results);
          }
        })
        .catch(reject);
    });
  });
}

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'foo'));
const promise3 = Promise.resolve(42);

customPromiseAll([promise1, promise2, promise3]).then((results) => {
  console.log(results); // Output: [3, "foo", 42]
});