Skip to content

Interview247/javascript-interview-questions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 

Repository files navigation

100 Must-Know JavaScript Interview Questions in 2025

web-and-mobile-development

You can also find all 100 answers here πŸ‘‰ Interview247 - JavaScript


1. What are the data types present in JavaScript?

JavaScript has two main categories of data types: primitive and non-primitive. There are seven primitive typesβ€”String, Number, BigInt, Boolean, Undefined, Null, and Symbolβ€”which are immutable. The single non-primitive type is Object, which is mutable and serves as the foundation for complex structures like arrays and functions.

JavaScript Data Types: An Overview

In JavaScript, data types classify the kind of value a variable can hold. As a dynamically-typed language, a variable's type is determined at runtime. The types are broadly divided into two categories: Primitive Types and the Non-Primitive Type (Object).

Primitive Data Types

Primitive types are fundamental, immutable data types, meaning their values cannot be changed once created. They are copied by value.

  • String: Represents a sequence of characters, enclosed in single or double quotes (e.g., 'hello').
  • Number: Represents both integer and floating-point numbers (e.g., 42, 3.14). It also includes special values like Infinity, -Infinity, and NaN (Not a Number).
  • BigInt: Represents whole numbers larger than the maximum safe integer that the Number type can represent. It's created by appending n to an integer literal (e.g., 9007199254740991n).
  • Boolean: Represents a logical entity with two values: true and false.
  • Undefined: Represents a variable that has been declared but has not yet been assigned a value.
  • Null: Represents the intentional absence of any object value. It's a primitive value that signifies "no value" or "empty."
  • Symbol: A unique and immutable primitive value that is often used as the key of an object property when you want to ensure the key is unique and won't conflict with other keys.

Non-Primitive (Structural) Type

Non-primitive types are used to store collections of data and more complex entities. They are mutable and are copied by reference.

  • Object: The primary non-primitive type, which represents a collection of key-value pairs (properties). Arrays, Functions, and Dates are all specialized types of objects in JavaScript.

Primitive vs. Non-Primitive Types

Characteristic Primitive Types Non-Primitive (Object)
Mutability Immutable (value cannot be altered) Mutable (internal state can be altered)
Storage Passed and stored by value Passed and stored by reference
Example let a = 5; let b = a; (b is a new copy of 5) let obj1 = {a: 5}; let obj2 = obj1; (obj2 points to the same object)

Code Examples

// Primitive Types
let str = "Hello, Interviewer!"; // String
let num = 100;                   // Number
let bigIntNum = 12345n;           // BigInt
let isDeveloper = true;          // Boolean
let notAssigned;                 // Undefined
let noValue = null;              // Null
let uniqueKey = Symbol('id');    // Symbol

// Non-Primitive Type
let person = {                 // Object
  name: "Alex"
  role: "Developer"
};

console.log(typeof str);         // "string"
console.log(typeof num);         // "number"
console.log(typeof noValue);     // "object" (This is a well-known quirk in JS)
console.log(typeof isDeveloper); // "boolean"
console.log(typeof uniqueKey);   // "symbol"
console.log(typeof person);      // "object"

2. What is the difference between null and undefined?

In JavaScript, undefined means a variable has been declared but has not been assigned a value; it's the default state. null, on the other hand, is an intentional assignment value that represents the explicit absence of any object value. While they are loosely equal (==), they are of different types; typeof undefined is 'undefined', whereas typeof null is famously 'object'.

In JavaScript, both null and undefined represent the absence of a value, but they have distinct meanings and use cases. Understanding the difference is fundamental to writing clean and predictable code.

Undefined

undefined is a primitive value that indicates a variable has been declared but has not yet been assigned a value. It is the default value for uninitialized variables, function parameters that are not provided, and the value returned by functions that do not explicitly return anything.

You typically encounter undefined in these situations:
  • A variable is declared but not initialized.
  • Accessing a non-existent property on an object.
  • A function doesn't have a return statement.
let uninitializedVar;
console.log(uninitializedVar); // -> undefined

const obj = { name: "Alice" };
console.log(obj.age); // -> undefined

function doNothing() {}
console.log(doNothing()); // -> undefined

Null

null is also a primitive value, but it represents the intentional absence of any object value. It is an assignment value, meaning a developer explicitly assigns null to a variable to signify that it holds no value. It's often used as a placeholder for an object that will be assigned later.

Example of using null:
let user = null; // Intentionally set to no value

// Later in the code, this might be assigned an object
user = { name: "Bob" };

Key Differences and Comparisons

The most important differences lie in their type and how they are used in equality checks.

Aspect undefined null
Meaning A variable has been declared but not assigned a value. An intentional assignment representing "no value".
Type of typeof undefined returns 'undefined'. typeof null returns 'object'. This is a well-known historical bug in JavaScript that can't be fixed due to backward compatibility.
Assignment It's often the default state. It is always explicitly assigned by a developer.

Equality Checks

When comparing them, null and undefined are loosely equal but not strictly equal.

console.log(null == undefined); // -> true (loose equality)
console.log(null === undefined); // -> false (strict equality, different types)

As a best practice, I rely on the language's default behavior for undefined and only use null when I need to explicitly and intentionally clear a variable's value or indicate an empty state where an object might otherwise be expected.


3. How does JavaScript handle type coercion?

JavaScript performs type coercion by automatically converting values from one data type to another when an operator is applied to values of different types. This implicit coercion can lead to unexpected results, which is why it's best practice to use the strict equality operator (===) and perform explicit type conversions using functions like Number() or String() when needed.

JavaScript handles type coercion by automatically converting a value from one data type to another when an operator is applied to values of different types. This is a fundamental characteristic of JavaScript as a loosely-typed language.

This process can be either implicit (automatic) or explicit (manual).

Implicit vs. Explicit Coercion

  • Implicit Coercion: This happens automatically behind the scenes when JavaScript anticipates the need for a type conversion. It's convenient but can sometimes lead to unexpected results if not fully understood.
  • Explicit Coercion: This is when the developer intentionally converts a value's type using built-in functions like Number(), String(), or Boolean(). This approach is clearer and less error-prone.

Common Coercion Scenarios

Let's look at the three main types of coercion in action.

1. String Coercion

The most common case is when using the addition (+) operator with a string value. If one operand is a string, the other will be converted to a string for concatenation.

// The number 5 is coerced to a string '5'
5 + "5"; // Returns '55'

// Other arithmetic operators coerce the string to a number
"10" - 5; // Returns 5
"10" * 5; // Returns 50

2. Boolean Coercion

This occurs in logical contexts, like conditional statements. JavaScript coerces values to true (truthy) or false (falsy).

There are only a few falsy values:

  • false
  • 0 (and -0, 0n)
  • '' (empty string)
  • null
  • undefined
  • NaN

Any other value is considered truthy.

if (0) {
  // This code will not run because 0 is falsy
}

if ("hello") {
  // This code will run because a non-empty string is truthy
}

3. Numeric Coercion (Using Abstract Equality)

This is famously demonstrated by the difference between the abstract equality (==) and strict equality (===) operators.

  • == (Abstract Equality): Compares two values for equality after performing type coercion.
  • === (Strict Equality): Compares two values for equality without performing any type conversion.

Because of the potential for bugs, it is strongly recommended to always use the strict equality operator.

Expression Result (==) Reason Result (===)
7 == '7' true String '7' is coerced to number 7 false
0 == false true Boolean false is coerced to number 0 false
null == undefined true A special equality rule in the spec false
'' == 0 true Empty string is coerced to number 0 false

Conclusion

To summarize, while JavaScript's automatic coercion can be helpful, it can also introduce subtle bugs. As a best practice, I always rely on strict equality (===) to prevent unintended coercion during comparisons and perform explicit type conversions when necessary to make my code predictable and easier to maintain.


4. Explain the concept of hoisting in JavaScript.

Hoisting is JavaScript's default behavior of moving declarations to the top of their scope before code execution. Consequently, var variables are initialized as undefined, while let and const variables are not initialized, leading to a "Temporal Dead Zone." Function declarations are fully hoisted, allowing them to be called before they are defined.

Understanding Hoisting in JavaScript

Hoisting is a fundamental concept in JavaScript that refers to its default behavior of moving declarations to the top of their containing scope during the compilation phase, before the code is executed. This means that you can use variables and functions before they are declared in your code. However, it's crucial to understand that only the declarations are hoisted, not the initializations.

Hoisting with var

When you declare a variable with var, its declaration is hoisted to the top of its function or global scope and is initialized with the value undefined. The assignment, however, remains in its original place.

// Example with var
console.log(myVar); // Outputs: undefined

var myVar = "Hello, Interviewer!";

console.log(myVar); // Outputs: "Hello, Interviewer!"

In the example above, the JavaScript engine processes var myVar; first, so when the first console.log is executed, myVar exists but its value is undefined.

Hoisting with let and const (Temporal Dead Zone)

Variables declared with let and const are also hoisted, but they are not initialized. They are placed in a state known as the Temporal Dead Zone (TDZ). The TDZ starts at the beginning of the block scope and ends when the declaration is encountered. Accessing these variables within the TDZ results in a ReferenceError.

// Example with let
console.log(myLetVar); // Throws: ReferenceError: Cannot access 'myLetVar' before initialization

let myLetVar = "This will not be reached";

This behavior enforces stricter code and helps prevent bugs that can arise from using variables before they are explicitly initialized.

Hoisting Functions

The way functions are hoisted depends on how they are defined: as a declaration or an expression.

Function Declarations

The entire function, including its name and body, is hoisted. This allows you to call a function before it appears in the source code.

// Example with a Function Declaration
sayHello(); // Outputs: "Hello!"

function sayHello() {
  console.log("Hello!");
}
Function Expressions

For function expressions, only the variable declaration (var, let, or const) is hoisted, following its specific hoisting rules. The function body itself is part of the assignment and is not hoisted.

// Example with a Function Expression using var
console.log(typeof greet); // Outputs: "undefined"
greet(); // Throws: TypeError: greet is not a function

var greet = function () {
  console.log("Greetings!");
};

Here, var greet is hoisted and initialized as undefined. Trying to invoke undefined as a function results in a TypeError.

Summary and Best Practices

To write clean and predictable code:

  • Prefer let and const over var to avoid hoisting-related confusion and leverage the safety of the Temporal Dead Zone.
  • Declare all variables and functions at the top of their scope. This makes the code more readable and aligns with how the JavaScript engine actually interprets it.
  • Understand the difference between function declarations and expressions to manage scope and availability effectively.

5. What is the scope in JavaScript?

In JavaScript, scope determines the accessibility of variables and functions during runtime. The primary types are Global Scope (accessible everywhere), Function Scope (variables declared with var are visible only within the function), and Block Scope (variables declared with let and const are visible only within their enclosing curly braces {}).

In JavaScript, scope is a fundamental concept that determines the accessibility and visibility of variables, functions, and objects in a particular part of your code during runtime. Essentially, it defines the context in which values and expressions are 'visible' or can be referenced. Properly managing scope is key to writing clean, predictable, and bug-free code.

Main Types of Scope

1. Global Scope

Variables declared outside of any function or block exist in the Global Scope. They can be accessed from anywhere in your JavaScript code, including from within functions and other blocks. While useful, it's generally best to avoid polluting the global scope to prevent naming conflicts.

// This variable is in the Global Scope
var globalVar = "I'm accessible everywhere!";

function showGlobal() {
  console.log(globalVar); // "I'm accessible everywhere!"
}

showGlobal();
console.log(globalVar); // "I'm accessible everywhere!"

2. Function Scope

When a variable is declared inside a function using the var keyword, it is only accessible within that function and any nested functions. This is known as Function Scope or Local Scope. Attempting to access it from outside the function will result in a reference error.

function myFunction() {
  var functionScopedVar = "I'm only visible inside this function.";
  console.log(functionScopedVar);
}

myFunction(); // Logs: "I'm only visible inside this function."
// console.log(functionScopedVar); // Throws Uncaught ReferenceError

3. Block Scope

Introduced in ES6 with the let and const keywords, Block Scope limits a variable's accessibility to the specific block of code (the statements enclosed in curly braces {}) in which it is defined. This is common in if statements, for loops, and while loops and allows for more granular control over a variable's lifecycle.

if (true) {
  let blockScopedVar = "Visible only inside this if-block.";
  const alsoBlockScoped = "Me too!";
  console.log(blockScopedVar); // Logs: "Visible only inside this if-block."
}

// console.log(blockScopedVar); // Throws Uncaught ReferenceError
// console.log(alsoBlockScoped); // Throws Uncaught ReferenceError

Lexical Scope

JavaScript uses lexical scoping (or static scoping). This means that the scope of a variable is determined by its position in the source code at the time of writing, not at runtime. An inner function has access to the scope of its outer functions, a concept that is fundamental to how closures work.

function outer() {
  let outerVar = "I am from the outer function";

  function inner() {
    // The inner function has access to outerVar due to lexical scoping
    console.log(outerVar);
  }

  inner();
}

outer(); // Logs: "I am from the outer function"

Scope Differences: var, let, and const

Keyword Scope Hoisting Can be Re-declared
var Function Scope Hoisted and initialized with undefined Yes (in the same scope)
let Block Scope Hoisted but not initialized (Temporal Dead Zone) No (in the same scope)
const Block Scope Hoisted but not initialized (Temporal Dead Zone) No (in the same scope)

In summary, understanding scope is crucial for managing variable lifecycle and avoiding common bugs like unintended variable overwrites. While var with its function scope was the original way, modern JavaScript development strongly favors let and const for their more predictable and stricter block-level scoping.


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

The == operator is the loose equality operator, which performs type coercion, meaning it converts operands to the same type before making the comparison. The === operator is the strict equality operator; it compares both the value and the type without any conversion. For robust and predictable code, it's best practice to always use ===.

In JavaScript, the fundamental difference between the == and === operators is how they handle type. The == operator, known as the Loose Equality operator, performs type coercion before comparison, while the === operator, or Strict Equality operator, compares both value and type without any conversion.

The == (Loose Equality) Operator

When you use the loose equality operator, JavaScript will attempt to convert the operands to the same type if they are not already. This can lead to results that might not be immediately obvious.

// Examples of type coercion with ==
console.log("1" == 1); // true, string "1" is converted to number 1
console.log(true == 1); // true, boolean true is converted to number 1
console.log(null == undefined); // true, a special case in the language spec
console.log(0 == false); // true, boolean false is converted to number 0

The === (Strict Equality) Operator

The strict equality operator is more predictable. It checks if the operands have the same value AND the same type. If the types are different, it will always return false, no questions asked.

// Same examples with ===
console.log("1" === 1); // false, because string is not a number
console.log(true === 1); // false, because boolean is not a number
console.log(null === undefined); // false, because they are different types
console.log(0 === false); // false, because number is not a boolean

Comparison Table

Aspect == (Loose Equality) === (Strict Equality)
Type Coercion Yes, converts operands to a common type. No, types must match.
Comparison Compares only value (after potential type conversion). Compares both value and type.
Predictability Can lead to unexpected behavior. Behavior is predictable and safe.
Use Case Generally discouraged. Only useful if you specifically need to compare values regardless of their type, like null == undefined. Recommended for almost all comparisons to ensure clarity and avoid bugs.

Conclusion and Best Practice

In an interview and in professional development, the answer is clear: always prefer strict equality (===). It makes your intentions explicit and your code more robust and easier to debug. Relying on the type coercion of == can hide bugs and make the code's logic harder to follow.


7. Describe a closure in JavaScript. Can you give an example?

A closure is a fundamental JavaScript concept where a function remembers and has access to its lexical scope, even when that function is executed outside of that scope. This allows an inner function to access the variables and parameters of its outer function, creating a persistent, private-like state.

Of course. A closure is a fundamental concept in JavaScript. It's a combination of a function and the lexical environment within which that function was declared. In simpler terms, a closure gives you access to an outer function’s scope from an inner function, even after the outer function has finished executing.

How It Works: Lexical Scoping

JavaScript's lexical scoping means that the scope of a function is determined by its location in the source code. When a function is defined inside another function, the inner function has access to the variables of the outer function. A closure is created when the inner function is returned from the outer function, because it maintains a reference to its outer lexical environment, effectively "closing over" the outer function's variables.

A Classic Example

Here’s a simple example to illustrate the concept:

function outerFunction() {
  const outerVariable = "I am from the outer scope!";

  function innerFunction() {
    // innerFunction has access to outerVariable
    console.log(outerVariable);
  }

  return innerFunction;
}

const myClosure = outerFunction(); // outerFunction has finished executing now
myClosure(); // logs: "I am from the outer scope!"

In this code, outerFunction executes and returns innerFunction. Even though outerFunction has completed, the returned function (now assigned to myClosure) still has access to outerVariable. This is the closure in action.

Practical Use Cases

Closures are not just a theoretical concept; they have very practical applications:

  • Data Privacy: Emulating private variables and methods.
  • Function Factories: Creating pre-configured functions.
  • Callbacks and Event Handlers: Maintaining state in asynchronous operations.

Example: Data Privacy

We can use a closure to create a "private" variable that cannot be accessed directly from the outside, only through the functions that have a closure over it.

function createCounter() {
  let privateCounter = 0;

  return {
    increment: function() {
      privateCounter++;
    }
    getValue: function() {
      return privateCounter;
    }
  };
}

const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getValue()); // logs: 2
console.log(counter.privateCounter); // logs: undefined (cannot be accessed directly)

Here, the privateCounter variable is protected by the scope of createCounter. The only way to interact with it is through the increment and getValue methods, which form a closure over that scope.


8. What is the this keyword in JavaScript and how does its context change?

The this keyword in JavaScript refers to the execution context of a function, and its value is determined by how the function is called. Its context can be the global object, the object a method is called on, a new instance when using the new keyword, or undefined in strict mode. Arrow functions are an exception, as they lexically inherit this from their parent scope.

The this keyword in JavaScript is a special identifier that refers to the execution context of a function. Its value is not static; it's determined dynamically at runtime based on how the function is called, not where it is defined. Understanding its behavior is crucial for writing predictable, object-oriented code.

How the Value of 'this' is Determined

  • Global Context: In the global scope (outside any function), this refers to the global objectβ€”window in browsers and global in Node.js.

    console.log(this); // In a browser, this logs the window object
  • Simple Function Call: When a regular function is called directly, this depends on whether the code is in strict mode. In non-strict mode, it defaults to the global object. In strict mode ('use strict';), it is undefined to prevent accidental modification of the global object.

    function showThis() {
      "use strict";
      console.log(this);
    }
    showThis(); // logs undefined
  • As an Object Method: When a function is called as a method of an object (e.g., myObject.myMethod()), this is bound to the object the method is called onβ€”the object to the left of the dot.

    const person = {
     name: 'Alice'
     greet: function() {
      console.log('Hello, ' + this.name);
     }
    };
    person.greet(); // logs 'Hello, Alice', because 'this' refers to 'person'
  • As a Constructor: When a function is invoked with the new keyword, it acts as a constructor. Inside that function, this refers to the newly created instance.

    function Car(make) {
      this.make = make;
    }
    const myCar = new Car("Toyota");
    console.log(myCar.make); // logs 'Toyota', because 'this' referred to the new 'myCar' instance

Special Case: Arrow Functions (ES6)

Arrow functions are a major exception. They do not have their own this context. Instead, they inherit this from their parent, or lexical, scope. This makes them extremely useful for callbacks and event handlers where you want to preserve the context of the enclosing method.

const user = {
 name: 'Bob'
 getFriends: function() {
  const friends = ['Charlie', 'David'];
  // Using an arrow function preserves the 'this' from getFriends
  friends.forEach(friend => {
   console.log(`${this.name} is friends with ${friend}`);
  });
 }
};
user.getFriends(); // Correctly logs "Bob is friends with..." for each friend

Explicitly Setting 'this'

You can also manually set the value of this for any function using one of three methods on the function's prototype: call(), apply(), or bind().

Method Description
.call(thisArg, arg1, arg2, ...) Invokes the function immediately, setting this and passing arguments individually.
.apply(thisArg, [argsArray]) Invokes the function immediately, setting this and passing arguments as an array.
.bind(thisArg) Returns a new function where this is permanently bound to the provided value. The new function can be called later.

9. What are arrow functions and how do they differ from regular functions?

Arrow functions, introduced in ES6, provide a more concise syntax for writing functions. Their most significant difference from regular functions is their handling of this, as they lexically bind it from the surrounding scope. Additionally, they cannot be used as constructors and don't have their own arguments object.

Introduction to Arrow Functions

Arrow functions, introduced in ES6, provide a more compact and syntactically clean way to write functions in JavaScript. Their two main benefits are a shorter syntax compared to traditional function expressions and the lexical binding of the this value, which simplifies managing scope.

Key Differences from Regular Functions

While arrow functions are a powerful addition, they are not a direct replacement for regular functions. They have specific behaviors and limitations. Here are the primary differences:

1. The this Keyword

This is the most fundamental difference. A regular function's this value is determined dynamically by how the function is called (the "call-site"). In contrast, an arrow function does not have its own this context; it inherits this lexically from its parent scope.

This is particularly useful in callbacks within methods:

// Regular function losing 'this' context
function Counter() {
  this.count = 0;
  setInterval(function () {
    this.count++; // 'this' is not the Counter instance, it's 'window' or 'undefined' in strict mode
    console.log(this.count); // logs NaN
  }, 1000);
}
// const c = new Counter();

// Arrow function preserving 'this' context
function ArrowCounter() {
  this.count = 0;
  setInterval(() => {
    this.count++; // 'this' is lexically bound to the ArrowCounter instance
    console.log(this.count); // logs 1, 2, 3... correctly
  }, 1000);
}
const ac = new ArrowCounter();

2. Syntax and Implicit Return

Arrow functions offer a much shorter syntax, especially for simple, one-line operations. If the function body is a single expression, you can omit the curly braces and the return keyword.

// Regular function
const add = function (a, b) {
  return a + b;
};

// Arrow function with explicit return
const addArrow = (a, b) => {
  return a + b;
};

// Arrow function with implicit return
const addArrowConcise = (a, b) => a + b;

// Arrow function with a single parameter
const square = (num) => num * num;

3. The arguments Object

Regular functions have a special, array-like object named arguments that contains all arguments passed to the function. Arrow functions do not have their own arguments object. Instead, you should use rest parameters to capture all arguments.

// Regular function
function logArgs() {
  console.log(arguments);
}
logArgs(1, 2, 3); // Logs [Arguments] { '0': 1, '1': 2, '2': 3 }

// Arrow function
const logArgsArrow = (...args) => {
  // console.log(arguments); // ReferenceError: arguments is not defined
  console.log(args);
};
logArgsArrow(1, 2, 3); // Logs [1, 2, 3]

4. Use as Constructors

Regular functions can be used as constructors with the new keyword to create instances. Arrow functions cannot be used as constructors and will throw a TypeError if you try. They also do not have a prototype property.

function Car(make) {
  this.make = make;
}
const myCar = new Car("Honda"); // Works

const ArrowCar = (make) => {
  this.make = make;
};
// const myArrowCar = new ArrowCar('Honda'); // TypeError: ArrowCar is not a constructor

Summary Table

Feature Regular Function Arrow Function
this binding Dynamic (depends on call-site) Lexical (from parent scope)
Constructor Can be used with new Cannot be used with new
arguments object Has its own arguments object Does not have arguments; use rest parameters
Syntax More verbose Concise, supports implicit return

When to Use Each

  • Use Arrow Functions for:
    • Callbacks (e.g., in .map(), .filter(), setTimeout) to preserve the this context.
    • Short, non-method functions where conciseness is valued.
  • Use Regular Functions for:
    • Object methods where you need this to refer to the object instance.
    • Constructors, as arrow functions cannot be used for this purpose.
    • Functions that need their own dynamic context or the legacy arguments object.

10. What are template literals in JavaScript?

Template literals are an ES6 feature that allows for more powerful string creation using back-ticks (`). They provide a cleaner syntax for embedding expressions via string interpolation with ${expression} and natively support multi-line strings without needing concatenation or escape characters.

Of course. Template literals, introduced in ES6, are a significant enhancement to how we handle strings in JavaScript. They are string literals that allow for embedded expressions and multi-line strings, using back-ticks (`) instead of single or double quotes.

They solve two major pain points from older versions of JavaScript: complex string concatenation and the awkward creation of multi-line strings.

Key Features of Template Literals

1. String Interpolation

This is arguably the most popular feature. It allows you to embed variables and expressions directly into a string in a clean and readable way using the ${expression} syntax. It's a huge improvement over traditional string concatenation.

Before ES6 (Concatenation)
const user = { name: "Alex", plan: "Premium" };
const welcomeMessage =
  "Welcome, " +
  user.name +
  "! You are subscribed to the " +
  user.plan +
  " plan.";
// Output: "Welcome, Alex! You are subscribed to the Premium plan."
With ES6 Template Literals
const user = { name: "Alex", plan: "Premium" };
const welcomeMessage = `Welcome, ${user.name}! You are subscribed to the ${user.plan} plan.`;
// Output: "Welcome, Alex! You are subscribed to the Premium plan."

As you can see, the ES6 version is much cleaner and less prone to errors from missing spaces or quotes.

2. Multi-line Strings

Template literals respect all whitespace and newlines within the back-ticks, making it incredibly easy to create multi-line strings without resorting to concatenation or escape characters (\).

Before ES6
const htmlString =
  "<div>\
" +
  "  <h1>Hello World</h1>\
" +
  "</div>";
With ES6 Template Literals
const htmlString = `
<div>
  <h1>Hello World</h1>
</div>`;

3. Tagged Templates (Advanced)

This is a more advanced feature where you can parse a template literal with a function. The function receives the string parts and the interpolated values as arguments, allowing you to perform custom logic, such as escaping HTML, internationalization, or creating domain-specific languages.

Example: A simple tag function
function style(strings, ...values) {
  let result = strings[0];
  values.forEach((value, i) => {
    // A simple example: wrap expressions in a <strong> tag
    result += `<strong>${value}</strong>` + strings[i + 1];
  });
  return result;
}

const name = "User";
const role = "Admin";
const styledMessage = style`The ${name} has the role: ${role}.`;

// Output: "The <strong>User</strong> has the role: <strong>Admin</strong>."

In summary, template literals are the modern, preferred way to handle strings in JavaScript because they are more powerful, readable, and concise than their predecessors.


11. What are Immediately Invoked Function Expressions (IIFEs) and when would you use them?

An Immediately Invoked Function Expression (IIFE) is a JavaScript function that is executed as soon as it is defined. Its primary purpose is to create a new scope, which prevents variables from polluting the global namespace and avoids naming conflicts. This pattern was essential for creating private state and modular code before ES6 modules became the standard.

An Immediately Invoked Function Expression, or IIFE (pronounced "iffy"), is a JavaScript design pattern where a function is defined and executed at the same time. It's a standard and effective way to manage scope and create data privacy.

The Syntax

The pattern is quite distinct. It involves wrapping an anonymous function in parentheses to make it a function expression, and then immediately invoking it with a second pair of parentheses.

(function () {
  // All the code inside this function is scoped to this function.
  // It runs immediately.
  console.log("This function ran as soon as it was defined!");
})();

Key Use Cases and Benefits

While the introduction of ES6 modules and block-scoped variables (let, const) has reduced their necessity, IIFEs were fundamental for writing clean, modular JavaScript for many years.

1. Avoiding Global Scope Pollution

This is the most common reason to use an IIFE. Any variables declared within the IIFE (using var, let, or const) are not added to the global scope. This prevents naming collisions between different scripts and libraries.

// Without an IIFE, 'user' becomes a global variable
// var user = 'Alice'; // This could conflict with other scripts

// With an IIFE
(function () {
  var user = "Alice";
  console.log("Inside the IIFE, user is:", user); // 'Alice'
})();

// console.log(user); // Throws ReferenceError: user is not defined

2. Creating Private State (The Module Pattern)

IIFEs are the foundation of the classic Module Pattern. You can create private variables and functions that are inaccessible from the outside world, while selectively exposing a public API by returning an object or function.

const counter = (function() {
  // This 'privateCount' variable is "private" and cannot be accessed from outside.
  let privateCount = 0;

  function changeBy(val) {
    privateCount += val;
  }

  // The returned object is our "public" API.
  return {
    increment: function() {
      changeBy(1);
    }
    decrement: function() {
      changeBy(-1);
    }
    value: function() {
      return privateCount;
    }
  };
})();

console.log(counter.value()); // 0
counter.increment();
counter.increment();
console.log(counter.value()); // 2
// console.log(counter.privateCount); // undefined, because it's private

Relevance in Modern JavaScript

In modern JavaScript (ES6+), the need for IIFEs has diminished. Block-scoping with let and const can create a private scope with a simple pair of curly braces {}, and ES6 Modules provide a much more robust, file-based system for encapsulation and avoiding global pollution. However, understanding IIFEs is still important for reading older codebases and for certain niche cases in modern development.


12. Can functions be assigned as values to variables in JavaScript (first-class functions)?

Yes, absolutely. Functions in JavaScript are first-class citizens, meaning they can be treated like any other variable. They can be assigned to variables, passed as arguments to other functions, and returned from other functions.

Yes, absolutely.

In JavaScript, functions are "first-class citizens," which is a fundamental concept. This means they are treated just like any other value, such as a number, string, or object. This property allows for powerful programming patterns and is a cornerstone of functional programming in JavaScript.

Treating functions as first-class citizens means they can be:

  • Assigned to variables or properties of an object.
  • Passed as arguments to other functions.
  • Returned as values from other functions.

1. Assigning a Function to a Variable

You can define a function and assign it to a variable, just like you would with a primitive value. This is often called a "function expression."

// Function expression assigned to a variable
const greet = function (name) {
  return `Hello, ${name}!`;
};

// We can now use the variable to invoke the function
console.log(greet("Alice")); // Output: Hello, Alice!

2. Passing a Function as an Argument

This is a very common pattern, especially for callbacks or in functional programming with higher-order functions. A higher-order function is simply a function that takes another function as an argument or returns a function.

function operate(a, b, operation) {
  return operation(a, b);
}

const add = (x, y) => x + y;
const multiply = (x, y) => x * y;

// Passing the 'add' and 'multiply' functions as arguments
console.log(operate(5, 3, add)); // Output: 8
console.log(operate(5, 3, multiply)); // Output: 15

3. Returning a Function from Another Function

A function can also create and return another function. This is often used to create closures, where the returned function "remembers" the environment in which it was created.

function createMultiplier(factor) {
  // This inner function is returned
  // It has access to the 'factor' variable from its parent scope (a closure)
  return function (number) {
    return number * factor;
  };
}

// createMultiplier returns a new function, which we assign to a variable
const double = createMultiplier(2);
const triple = createMultiplier(3);

console.log(double(10)); // Output: 20
console.log(triple(10)); // Output: 30

In summary, the ability to treat functions as values is what makes JavaScript so flexible and enables powerful features like callbacks, event listeners, and many modern programming paradigms.


13. What is a higher-order function in JavaScript?

A higher-order function in JavaScript is a function that either takes one or more functions as arguments or returns a function as its result. This concept is fundamental to functional programming, enabling powerful abstractions and code reusability.

In JavaScript, a higher-order function is a function that operates on other functions. Specifically, it can do one or both of the following:

  • Take one or more functions as arguments.
  • Return a function as its result.

This paradigm is a cornerstone of functional programming in JavaScript, allowing for more abstract, reusable, and composable code.

1. Functions as Arguments (Callbacks)

One of the most common applications of higher-order functions is when a function accepts another function as an argument. These argument functions are often referred to as callbacks, as they are "called back" at a later point during the execution of the higher-order function to perform a specific action or customization.

Example: Iterating with a Callback

function forEach(arr, callback) {
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i], i, arr);
  }
}

const numbers = [1, 2, 3];
forEach(numbers, function (num, index) {
  console.log(`Element at index ${index}: ${num}`);
});
// Output:
// Element at index 0: 1
// Element at index 1: 2
// Element at index 2: 3

Many built-in JavaScript array methods like map(), filter(), reduce(), and forEach() are higher-order functions that accept callback functions.

2. Functions as Return Values (Function Factories/Closures)

Another powerful aspect is when a function returns another function. This is often used to create specialized functions, create closures to maintain state, or for techniques like currying or memoization.

Example: A Function Factory

function createMultiplier(factor) {
  return function (number) {
    return number * factor;
  };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);

console.log(double(5)); // Output: 10
console.log(triple(5)); // Output: 15

In this example, createMultiplier is a higher-order function that returns a new function (double or triple), which "remembers" the factor from its lexical environment due to closures.

Benefits of Using Higher-Order Functions

  • Abstraction and Reusability: They allow you to abstract away common patterns and encapsulate logic, making your code more modular and easier to reuse.
  • Composability: You can combine simpler functions to build more complex functionality, leading to more maintainable code.
  • Declarative Style: They often lead to code that describes "what" is being done rather than "how," improving readability.
  • Reduced Boilerplate: By abstracting common operations, you can write less repetitive code.

Common Built-in Higher-Order Functions

JavaScript provides many built-in higher-order functions, especially for array manipulation:

  • Array.prototype.map()
  • Array.prototype.filter()
  • Array.prototype.reduce()
  • Array.prototype.forEach()
  • Array.prototype.sort()

14. What is the difference between var, let, and const?

var is function-scoped and hoisted with an initial value of undefined. let and const are block-scoped and hoisted but remain in a "temporal dead zone" until their definition is evaluated. let allows reassignment, while const creates a read-only reference to a value, meaning the variable identifier cannot be reassigned (though the contents of objects or arrays it points to can still be modified).

In modern JavaScript, let and const are preferred over var due to their predictable scoping behavior.

1. Scope

  • var (Function Scope): Variables declared with var are scoped to the nearest function block. If declared outside any function, they are globally scoped. They ignore block scopes like if statements or loops.
  • let & const (Block Scope): These are scoped to the nearest enclosing block (denoted by curly braces {}). This makes them safer to use in loops and conditionals.
if (true) {
  var a = 1;
  let b = 2;
  const c = 3;
}
console.log(a); // 1 (accessible)
// console.log(b); // ReferenceError
// console.log(c); // ReferenceError

2. Hoisting

  • var: Hoisted to the top of its scope and initialized with undefined. You can access it before its declaration without an error (though it will be undefined).
  • let & const: Hoisted to the top of their block but not initialized. Accessing them before the declaration results in a ReferenceError. This period is known as the Temporal Dead Zone (TDZ).
console.log(x); // undefined
var x = 5;

// console.log(y); // ReferenceError
let y = 10;

3. Reassignment and Redefinition

  • var: Can be re-declared and re-assigned within the same scope.
  • let: Can be re-assigned but cannot be re-declared in the same scope.
  • const: Cannot be re-assigned or re-declared. It must be initialized at the time of declaration.

Note on const: While the variable binding is immutable, the value itself (if it's an object or array) is still mutable.

const obj = { name: "Alice" };
obj.name = "Bob"; // Allowed: modifying the object property
// obj = {}; // Error: Assignment to constant variable

Summary Table

Feature var let const
Scope Function Block Block
Hoisting Yes (init undefined) Yes (TDZ) Yes (TDZ)
Reassignable Yes Yes No
Redeclarable Yes No No

15. What is the "Temporal Dead Zone" (TDZ) in JavaScript?

The Temporal Dead Zone (TDZ) is a behavior in JavaScript where variables declared with let and const are inaccessible before their declaration line is executed. Although these variables are hoisted to the top of their block scope, they are not initialized, unlike var variables which are initialized with undefined. Accessing them in this zone throws a ReferenceError.

The Temporal Dead Zone (TDZ) is a specific period in the execution of a JavaScript program where a variable exists but cannot yet be accessed. This concept applies specifically to variables declared with let and const (and also class).

How It Works

When a block of code (like a function or an if statement) is entered, the JavaScript engine allocates memory for all variables declared in that scope. This is known as hoisting.

  • var variables: Are hoisted and immediately initialized with undefined.
  • let and const variables: Are hoisted but remain uninitialized. They sit in the "Temporal Dead Zone."

The TDZ starts at the beginning of the scope and ends when the line of code that declares the variable is executed.

Code Example

{
  // TDZ starts here for 'myVar'
  console.log("Hello");

  // console.log(myVar); // Throws ReferenceError: Cannot access 'myVar' before initialization

  let myVar = 10; // TDZ ends here
  console.log(myVar); // 10
}

Why Does It Exist?

The TDZ was introduced in ES6 to:

  1. Catch Bugs: Accessing a variable before it's declared is usually an accident. The TDZ makes this an error rather than silently returning undefined (as var does).
  2. Enable const: For const to work properly, it must be assigned a value at the moment it is initialized. The TDZ ensures you can't use a constant before it has its value.

Best Practice

Always declare your variables at the top of their scope to avoid running into the Temporal Dead Zone and to make your code cleaner and easier to reason about.


Explore all 100 answers here πŸ‘‰ Interview247 - JavaScript


web-and-mobile-development

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published