50 Frontend Interview Questions - JavaScript
Published: 2024-01-07 18:56:54

Introduction
A large number of beginner and experienced developers have started to encounter and notice a problem with job searching. Indeed, the level of competition in the market has become much higher than it was 2-3 years ago.

The best thing to do in the current reality is to keep learning.
In this article, I have compiled 52 questions in a "question:answer" format for frontend developers that I have come across during interviews. The questions are mainly geared towards Junior level and partially towards Middle level.

P.S. Despite the fact that most of the questions are aimed at the Junior level, by preparing for these questions and thoroughly studying the topics, I was able to secure a job as a Middle Frontend Developer.

**1. What data types exist in JavaScript?**

1-Number - Numbers
2-String - Strings
3-Boolean - Boolean type, true or false
4-Object - JavaScript object
5-null - a special value that represents "nothing", "empty", or "unknown value".
6-undefined - "value has not been assigned". This type is assigned if a variable is declared but has no assigned value.
7-Symbol - a unique and immutable data type that can be used as an identifier for object properties.
8-BigInt - used for creating large numbers.

**2. What is the difference between "==" and "==="?**

The operator "==" checks for abstract equality, while "===" checks for strict equality.
In other words, the "==" operator performs necessary type conversions before comparison, whereas "===" does not perform type conversion. Therefore, if two values are not of the same type, it will return false when using the "===" operator.

**3. What are the ways to declare a variable?**

There are 4 ways to declare a variable:

```
  foo = 123; 
  var foo = 123; 
  let a = 123; 
  const a = 123;
```

Declaring a variable using the "var" keyword is similar to the first method. Variables declared this way have global or function scope and lack block scope, which is a disadvantage.
"let" and "const" are preferable ways to declare variables. They have block scope, meaning that a variable declared inside, for example, a function, will not be visible outside of that function. "const" variables are immutable, but if it's an object, you can change its properties, and if it's an array, you can modify and add elements.

**4. What is the difference between null and undefined?**

Both options represent an empty value. If we initialize a variable but don't assign a value to it, it will be assigned a special marker - undefined. Null is assigned manually.

Null is a special value that represents "nothing," "empty," or "unknown value." If we need to clear the value of a variable, we set foo = null.


**5. Arrow functions and the differences from regular functions.**

Arrow functions cannot use the arguments object.
They have a different syntax.
Arrow functions do not have their own this context. When referencing this, an arrow function takes the context from the surrounding scope.
Arrow functions cannot be used as constructor functions. In other words, they cannot be invoked with the new keyword.

**6. What is a closure and why are they needed?**

A closure is a function along with all the external variables that it has access to. For example, there is a function that has a nested function which will close over and retain the variables from its parent.

```
function parent() { 
    const a = 5; 
    return function child() { 
        console.log(5); // child closes over the variable 'a'; 
    } 
}

```

**7. What are template literals?**

Template literals are enclosed in backticks () and allow for multiline strings. They also allow for embedding expressions within them.

```
const name = 'John';
const text = `User's name is ${name}`;

console.log(text) // User's name is John
```
**8. What are Set and Map?**

Map is a collection, a data structure that operates on the principle of key-value pairs, similar to Objects. However, the main difference between Map and Object is that Map allows the use of keys of any type.
Set is a type of collection without keys, an array where each value can only appear once. Set stores unique values within itself.


**9. How to check for the presence of a property in an object?**

The first way is to use the hasOwnProperty function, which is available for every object.
The second way is to use the in operator. However, when using the in operator, caution must be exercised as it checks all prototypes in the chain.

```
const obj = {
  year: 2023,
  name: "John"
}

console.log(obj.hasOwnProperty("year")) // true
console.log("year" in obj) // true
console.log("ye" in obj) // false
```

**10. How to access an object property?**

The first way is static, using dot notation: obj.a.
The second way is dynamic, using square brackets: obj['a'].

```
const obj = {
  year: 2023,
  name: "John"
}

console.log(obj['year']) // 2023
console.log(obj.name) // John
```

**11. What are the main methods for working with arrays?**

- 1. forEach - an iterative method for looping through the array, does not return anything. It offers a more elegant alternative to a regular for loop.

- filter(callback, [args]) - a method for filtering an array using a provided function. It creates a new array that includes only the elements from the original array for which the callback(item, i, arr) function returns true.

- map(callback, [args]) - a method for transforming an array. It creates a new array that consists of the results of calling the callback(item, i, arr) function for each element of the array.

- reduce(callback, [initValue]) - a method for sequentially processing each element of the array while maintaining an intermediate result.

**12. What are the ways to create an object?**

Using a constructor function:

```
function User(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}
const user = new User('John', 'Johnson');
console.log(user); // { firstName: 'John', lastName: 'Johnson' }
```
Using object literal notation:

```
const user = {
  firstName: 'John',
  lastName: 'Johnson'
};
```
Using a class:

```
class User {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}
const user = new User('John', 'Johnson');
console.log(user); // { firstName: 'John', lastName: 'Johnson' }
```
Using the create function:

```
const user = Object.create({
  firstName: 'John',
  lastName: 'Johnson'
});
```

**13. What is a Promise?**

A Promise is an object designed to work with asynchronous code. It maintains its own state. Initially, a Promise is in the pending state, then it transitions to the fulfilled state if the asynchronous code is executed successfully, or to the rejected state if an error occurs. A Promise accepts two callback functions:

onFulfilled, which is triggered when the Promise is fulfilled.
onRejected, which is triggered when the Promise is rejected.
The usage pattern is as follows:

The code that needs to perform something asynchronously creates a Promise and returns it.
The external code, upon receiving the Promise, passes the onFulfilled and onRejected callback functions to it.
Upon completion of the process, the asynchronous code transitions the Promise to the fulfilled or rejected state, automatically invoking the corresponding callback function.

**14. What is async/await and how to use it?**

async/await is a special syntax for working with Promises.
A function declared with the async syntax always returns a Promise.
The keyword await makes the JavaScript interpreter wait until the Promise on the right side of await is fulfilled before continuing the execution. It will then return the result, and the code execution will proceed. await cannot be used in regular functions.


**15. How to check if an object is an array?**

To check whether an object is an array or not, you can use the Array.isArray() method. It takes an object as input and returns true if the object is an array, and false if it is not an array.

```
const obj1 = { person: 'John' }
const obj2 = new Array(2)
const obj3 = []

console.log(Array.isArray(obj1)) // false
console.log(Array.isArray(obj2)) // true
console.log(Array.isArray(obj3)) // true
```

**16. What is the purpose of the spread operator?**

The spread operator (...) is used to unpack arrays or objects.
It allows you to expand elements that are iterable, such as arrays and strings.

- It is used in functions where the expected number of arguments for a call is zero or more.

- It is used in array literals or expressions.

- It is used in object literals where the number of key-value pairs should be zero or more.

```
const date = [2000, 3, 7] 
const newArray = [...date] // [2000, 3, 7]
```

**17. How to avoid reference dependency when copying an object?**

If the object does not contain nested objects, for example:

```
const obj = {
 firstName: 'John',
 lastName: 'Johnson'
}

```
In this case, you can use spread operator or Object.assign() method:

```
const copy = {...obj}
// or
const copy = Object.assign({}, obj)
```

If the object contains nested objects:

```
const obj = {
    data: {
        id: 1
    }
}
```
In this case, you need to perform a deep copy.
A workaround, though slower, is:

```
const copy = JSON.parse(JSON.stringify(obj))

```
This method is suitable for objects without prototypes and functions.
Alternatively, you can use the lodash library's deepClone() function.

**18. How to change the context of a function?**

Using the bind() method, which returns a new function with the bound context.

```
function foo() {
    return this
}
const obj = { name: 'John' }
const newFoo = foo.bind(obj)
console.log(newFoo()) // { name: 'John' }
```
Using call() and apply() methods. The main difference is that call() accepts a sequence of arguments, while apply() accepts an array of arguments as the second parameter.

```
function foo() {
    return this
}

const obj = { name: 'John' }
foo.call(obj, 'arg1', 'arg2') // { name: 'John' }
foo.apply(obj, ['arg1', 'arg2']) // { name: 'John' }

```
**19. What is a ternary operator?**

A ternary operator is a shorthand notation for an if-else statement. The operator is represented by a question mark and a colon. It is called ternary because it is the only operator that takes three arguments.

Condition ? Expression_1 : Expression_2

```
num >= 10 ? 'more than 10' : 'less than 10'
// is equal to
if (num >= 10) {
    return 'more than or equal to 10'
}
return 'less than 10'
```
**20. What is destructuring?**

Destructuring is a syntax that allows us to unpack arrays and objects into multiple variables.

```
const arr = ['John', 'Johnson']
const [firstName, lastName] = arr
console.log(firstName, lastName) // John Johnson

OR

const obj = {
    firstName: 'John',
    lastName: 'Johnson'
}
const { firstName, lastName } = obj;
console.log(firstName, lastName) // John Johnson
```

**What is the DOM?**

DOM stands for Document Object Model. It is a representation of an HTML document as a tree of tags.
Example
Each node in the DOM tree is an object.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rjb8acfwndif9121eqjl.png)

The basic elements of an HTML document are tags.
According to the Document Object Model (DOM), each HTML tag is an object. Nested tags are "children" of their parent element. The text inside a tag is also an object. All these objects are accessible using JavaScript, and we can use them to manipulate the page.

**22. What is the Event Loop?** 

Event loop - a mechanism that manages the execution of code. It handles event processing and task execution in the correct order. The main idea of the event loop is that JavaScript runs in a single-threaded environment but can handle asynchronous operations. When an asynchronous operation, such as a server request, completes, it puts the corresponding event into the event queue. The event loop works in a loop, processing these events in the order they arrive. It takes an event from the queue and passes it for execution. If the event contains a callback or a handler, it is invoked, and the code associated with that event is executed. The event loop also handles other tasks, such as timers and microtasks (Promise). It manages the execution order of all these tasks to ensure consistency and prevent the blocking of the main thread of code execution.

In short, the event loop in JavaScript manages asynchronous operations by handling events in the queue and executing the corresponding code in the correct order. This allows JavaScript to be responsive and effectively utilize its resources when working with asynchronous operations.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8qv34q3fuj7zpkfl131u.png)

I highly recommend watching the video at the link provided, as the topic is important and deserves a separate article.

**23. What is prototypal inheritance?**

Every object in JavaScript has a property - a prototype. Methods and properties can be added to the prototype. Other objects can be created based on the prototype. The created object automatically inherits the methods and properties of its prototype. If a property is absent in the object, its search will be performed in the prototype.


**24. What is the Optional Chaining operator?**

The Optional Chaining operator ?. stops the evaluation and returns undefined if the part after ?. is either undefined or null.

Let's consider a user object. Most users have an address user.address, with a street user.address.street, but some users have not provided an address. In such cases, the Optional Chaining operator can help us avoid an error when trying to access the user's street who hasn't specified one in their address.

```
const user = {};

console.log(user.address.street) // Error!
console.log(user?.address?.street) // undefined. No Error
```

**25. What is Shadow DOM?**

Shadow DOM is a set of web standards that allows for encapsulating the structure and styles of elements on a web page. It represents a special segment of the DOM that resides inside an element and is separate from the rest of the page. Shadow DOM is used to create components and widgets with isolated and stylized content that does not conflict with the overall structure of the page.


26. What is recursion? How to use it?
Recursion is an approach to problem-solving where a function solves a problem by reusing itself within its own function body. In simple terms, it's when a function calls itself.

A recursive function consists of:

1. Termination condition or base case
2. Recursive step - a way to reduce the problem into simpler forms.

```
function factorial(x) {
   if (x === 0) {
      return 1;
   }
   return x * factorial(x - 1);
}
```
The base case is a necessary condition; otherwise, it will lead to stack overflow due to an infinite loop of function calls.

**27. What's the difference between Function Expression and Function Declaration?**

Function Declaration is the traditional way of declaring a 
function.

```
function foo() {
    console.log('Hello World');
}
```

Function Expression:

```
let foo = function() {
    console.log('Hello World');
}
```
With Function Declaration, the function is created and assigned to a variable, just like any other value. Essentially, it doesn't matter how the function is defined, as it is a value stored in the variable "foo". Function Declarations, however, are processed before the code block is executed, meaning that they are visible throughout the entire code block. On the other hand, Function Expressions are created only when the execution flow reaches them.


**28. What are constructor functions?**

Constructor functions are regular functions that are used to create objects. However, there are two rules for using them:

1. The name of the constructor function should start with a capital letter.
2. The constructor function should be called using the new operator.

```

function User(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
    this.role = 'user'
}

const user = new User('John', 'Johnson')
console.log(user.firstName) // John
```
When a constructor function is created using the new operator, the following happens:

1. A new empty object is created and assigned to this.
2. The code inside the constructor function is executed. Typically, this code will modify the this object and add new properties.
3. The value of this is returned.

**29. How can you get a list of keys and a list of values from an object?**

You can use Object.keys() to get a list of keys and Object.values() to get a list of values.

```
const user = {
    firstName: 'John',
    lastName: 'Johnson'
}

const keys = Object.keys(user)
const values = Object.values(user)

console.log(keys) // ['firstName', 'lastName']
console.log(values) // ['John', 'Johnson'] 

```

**30. Provide an example of new functionality in ES6.**

The most common ones:

- let and const. Introduction of new keywords let and const for declaring variables with block scope.
- Arrow functions. The concept of arrow functions allows for more concise and clear function definitions.

```
function add(a, b) { return a + b } // Regular function
const add = (a, b) => a + b // Arrow function
```
Default parameters. You can define default values for function parameters.

```
function greet(name = 'Anonymous') { console.log(Hello, ${name}!) }
greet(); // "Hello, Anonymous!"
greet('John') // "Hello, John!"
```
Spread operator (...). The spread operator allows unpacking array or object elements for function arguments or creating new arrays/objects.

```
const numbers = [1, 2, 3];
console.log(...numbers) // 1 2 3
const array1 = [1, 2, 3]; 
const array2 = [...array1, 4, 5] // [1, 2, 3, 4, 5]
```
Destructuring. Destructuring allows extracting values from arrays or objects and assigning them to variables.

```
const person = { name: 'John', age: 30, city: 'London' }
const { name, age } = person;
console.log(name, age) // "John 30"
const numbers = [1, 2, 3]
const [first, second] = numbers;
console.log(first, second); // 1 2
```

**32. What are micro and macro tasks in JavaScript?**

In JavaScript, microtasks and macrotasks refer to types of tasks that need to be executed in the event loop. Microtasks are tasks that need to be executed within the current event loop before the browser repaints the page. They are usually added to the execution queue using methods such as Promise.then(), process.nextTick() (in Node.js), or MutationObserver. Examples of microtasks include executing promise handlers and DOM mutations. On the other hand, macrotasks are tasks that need to be executed after the current event loop is finished and before changes are rendered on the screen. This includes tasks added to the event queue using setTimeout, setInterval, requestAnimationFrame, as well as handling input events and network requests. Macrotasks are executed after all microtasks in the current event loop have been processed. The difference between microtasks and macrotasks is important because it determines the order of execution and allows for managing the priority of different tasks in JavaScript. Microtasks have a higher priority and are executed before macrotasks, which allows for faster interface updates and prevents blocking the main JavaScript execution thread.

**33. What are generators?**

Generators produce a sequence of values one by one as needed. Generators work well with objects and make it easy to create data streams.

To declare a generator, a special syntax is used - a generator function.

```
function* generateSomething() {
    yield 10;
    yield 20;
    yield 30;
    return 40;
}
```
next() is the main method of a generator. When called, next() starts executing the code until the nearest yield statement. The value may be absent, in which case it is represented as undefined. When a yield is reached, the function execution is paused, and the corresponding value is returned to the outer code.

```
let generator = generateSomething();
let first = generator.next();
```

**34. What are the methods of storing data in a browser?**

There are several methods of storing data in a browser:

LocalStorage and SessionStorage - store key-value pairs in the browser. The data stored in them is retained after the page is refreshed. Both storage options can only use strings as keys and values, so objects need to be converted using JSON.stringify().
Cookie - small strings of data that are stored in the browser. Cookies are usually set by the web server using the Set-Cookie header. The browser will then automatically add them to almost every request to the same domain using the Cookie header. One cookie can hold up to 4kb of data. Depending on the browser, more than 20 cookies per site are allowed.
IndexedDB - a built-in database, more powerful than localStorage. It is a key-value store where multiple types of keys are available and values can be almost anything. IndexedDB supports transactions for reliability, supports key range queries and indexes, and allows storing more data than localStorage. IndexedDB is designed for offline applications and can be combined with Service Workers and other technologies.


**35. What is the difference between sessionStorage and localStorage?**

SessionStorage and localStorage allow storing objects in key-value format in the browser.
The main differences are:

localStorage can store up to 10 MB of data, while sessionStorage can store up to 5 MB.
Data in localStorage is not deleted, while data in sessionStorage is deleted when the browser tab is closed.
Data from localStorage is accessible from any window, while data from sessionStorage is only accessible from the same browser window. Learn more
36. What are regular expressions?
Regular expressions are strings defined by special rules and patterns. They are a powerful tool that allows detecting and working with complex constructions within strings.

```
let str = "We will, we will rock you"

console.log(str.match(/we/gi)) // ['We', 'we']

```
**37. What are WeakSet and WeakMap and how do they differ from Map and Set? **

The first difference between WeakMap and Map is that the keys in WeakMap must be objects, not primitive values.
The second difference is in the memory storage of the data structures. The JavaScript engine keeps values in memory as long as they are reachable, meaning they can be used.
Usually, object properties, array elements, or other data structures are considered reachable and are kept in memory as long as the data structure exists, even if there are no other references to them.
In the case of WeakMap and WeakSet, it works differently. Once an object becomes unreachable, it is removed from the data structure.


**38. Why do two objects with the same fields return false when compared?**
Objects are compared based on references to the memory area. For JavaScript, test1 and test2 objects are different, even though they have the same fields. Objects are only equal if they are the same object.

```
const test1 = { value: 3 }
const test2 = { value: 3 }
console.log(test1 == test2) // false
```

**39. Why can we call methods on primitive types?**

JavaScript allows working with primitive data types - strings, numbers, etc. - as if they were objects. Primitive data types have methods.
To make this functionality available, each primitive data type has its own wrapper object: String, Number, Boolean, and Symbol. Thanks to these wrapper objects, primitive data types have different sets of methods, such as toLowerCase() or toUpperCase().

**40. How to check which class an object was created from?**

You can check which class an object was created from using the instanceof operator, taking inheritance into account.

```
class Person {}
const person = new Person()
console.log(person instanceof Person) // true

```
**41. Write code that will log the time spent on the site in seconds every 10 seconds.**

```
let time = 0
setInterval(() => {
    time += 10
    console.log(time)
}, 10000)
```

**42. What is a pure function?**

A pure function is a function that satisfies two conditions:

Every time the function is called with the same set of arguments, it returns the same result.
It has no side effects, meaning it does not modify variables outside the function.

```
function calculate(num) {
    return calculate * 0.05;
}
console.log(calculate(15)) 
//calculate() function will always return the same result if we pass the same parameter
```

**43. What is a higher-order function?**

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

```
const nums1 = [1, 2, 3]
const nums2 = nums1.map(function(num) {
    return num * 2;
})
console.log(nums2) // [2, 4, 6]
```

**44. Why do we need Promises if we can work with asynchronous code using callbacks?**

If we want to asynchronously fetch some data from a server using callback functions, it would result in the following:

```
func((x) => {
  anotherFunc(x, (y) => {
    andAnotherFunc(i, (j) => {
      // some code
    })
  })
})
```
This is called callback hell, as each callback is nested inside another, and each inner callback depends on the parent function.

Using Promises, we can rewrite the code above:

```
func()
.then((x) => {
  return anotherFunc(x)
})
.then((y) => {
  return andAnotherFunc(y)
})
.then((i) => {
  return i
})
```
With Promises, the execution sequence is clear, making the code more readable.

**45. Write your own implementation of the bind method.**

To implement it, we can use closure and the apply() method to bind the function to the context.

```
function bind(context, func) {
  return function(...args) {
    func.apply(context, args)
  }
}
```

**46. Write a calculator function with methods plus, minus, multiply, divide, and get. The function must work through optional chaining.**

```
function calculator() {
  let result = 0;

  function plus(val) {
    result += val;
    return this;
  }

  function minus(val) {
    result -= val;
    return this;
  }

  function divide(val) {
    result /= val;
    return this;
  }

  function multiply(val) {
    result *= val;
    return this;
  }

  function get() {
    console.log(result);
    return this;
  }

  return { plus, minus, divide, multiply, get };
}

let calc = calculator();
calc.plus(2).minus(1).plus(19).divide(2).multiply(3).get(); // 30
```

**47. Write a randomSort function that takes an array of numbers and sorts the array in random order.**

You can use the sort() method and Math.random() for this.

```
function randomSort(array) {
  return array.sort(() => {
    return 0.5 - Math.random();
  });
}
const arr = [2, 1, 3, -2, 9]
console.log(randomSort(arr)) // [-2, 2, 1, 3, 9]
console.log(randomSort(arr)) // [2, 1, -2, 9, 3]
console.log(randomSort(arr)) // [-2, 1, 9, 2, 3]
console.log(randomSort(arr)) // [1, -2, 2, 3, 9]
```

**48. Write a deleteGreatestValue function that takes a two-dimensional array of numbers and removes the greatest number from each nested array.**

We should iterate through every nested array, get the greatest value of each nested array and delete it.

```
function deleteGreatestValue(array) {
  for (let i = 0; i < array.length; i++) {
    const max = Math.max(...array[i]);
    const maxIndex = array[i].indexOf(max);
    array[i].splice(maxIndex, 1);
  }
  return array;
}

const arr = [[1, 4, 4], [2, 6, 3], [9, 2, 7]]
console.log(deleteGreatestValue(arr)) // [[1, 4], [2, 3], [2, 7]]
```

**49. Write a sortPeople function that takes an array of strings names and an array of numbers heights, where names[i] == heights[i]. It should sort the names array based on the heights array.**

```
function sortPeople(names, heights) {
  const array = [];
  for (let [i, name] of names.entries()) {
    array.push([name, heights[i]]);
  }
  return array.sort((a, b) => b[1] - a[1]).map(([name]) => name);
}
const names = ['John', 'Maria', 'Alexa', 'Robert']
const heights = [180, 160, 165, 187]
console.log(sortPeople(names, heights)) // ['Robert', 'John', 'Alexa', 'Maria']
```

**50. Write a subsets function that takes an array of numbers nums and returns all possible variations of arrays from those numbers.**

```
function subsets(nums) {
  let result = [[]];

  for (let num of nums) { // Iterate through each number in the nums array
    const currentSize = result.length; // Get the current size of result to use it in the loop.

    for (let i = 0; i < currentSize; i++) {
      let subArray = [...result[i], num]; // Create a new subarray by adding the current number to the result[i] element.
      result.push(subArray);
    }
  }

  return result; // Return all possible variations of arrays from the numbers.
```