As the backbone of interactive web applications, JavaScript remains essential for building dynamic, responsive, and scalable front-end experiences—and increasingly for server-side development through Node.js. Recruiters need developers who can write clean, efficient, and modern JavaScript code while leveraging the latest ECMAScript (ES6+) features.
This resource, "100+ JavaScript Interview Questions and Answers," is tailored for recruiters to streamline the evaluation process. It covers topics from core language fundamentals to advanced concepts, ensuring you can accurately assess a candidate’s capabilities.
Whether hiring for Front-End Developers, Full-Stack Engineers, or JavaScript Specialists, this guide enables you to assess a candidate’s:
Core JavaScript Knowledge
let
/const
, template literals, destructuring, spread/rest operators, and modules.Advanced Skills
async/await
, and the Event Loop.Real-World Proficiency
For a streamlined assessment process, consider platforms like WeCP, which allow you to:
✅ Create customized JavaScript assessments tailored to your project’s stack and complexity.
✅ Include hands-on coding tasks, such as implementing algorithms, manipulating the DOM, or building small applications.
✅ Proctor assessments remotely with AI-driven monitoring for test integrity.
✅ Leverage automated grading to evaluate code correctness, efficiency, and adherence to ES6+ best practices.
Save time, ensure technical excellence, and confidently hire JavaScript developers who can deliver interactive, high-performance web applications from day one.
JavaScript is a high-level, interpreted programming language that was initially created to add interactivity and dynamic functionality to web pages. It was developed by Brendan Eich at Netscape in 1995 under the name "LiveScript," and later renamed JavaScript to align with the growing popularity of Java. Despite the name similarity, JavaScript and Java are distinct languages with different use cases and characteristics.
JavaScript is a multi-paradigm language that supports event-driven, functional, and imperative programming styles. It's primarily used on the client side in web browsers to manipulate the Document Object Model (DOM), allowing developers to change the content and structure of web pages dynamically without requiring a full page reload.
With the advent of environments like Node.js, JavaScript is now also used on the server side for building web applications, APIs, and server services. This has led to a unified development stack, often referred to as the "JavaScript stack" (with technologies like Express, MongoDB, Angular/React/Vue, and Node.js—commonly abbreviated as MEAN or MERN). JavaScript is fundamental to the "full stack" development world and has become one of the most popular and widely-used programming languages in the world.
Despite the similar names, Java and JavaScript have significant differences in terms of design, use cases, syntax, and paradigms:
In JavaScript, data types are divided into two categories: primitive types and objects. Here's a deeper look at each:
Primitive Data Types:
Objects:
Objects are more complex data structures that can hold multiple values and data types. They are used for storing collections of data and more complex entities. Examples of objects include arrays, functions, and plain JavaScript objects created using curly braces.
A variable in JavaScript is a symbolic name for a value stored in memory. It is a container that holds data, and this data can be accessed and modified through the variable's name. Variables in JavaScript are dynamic, meaning they can change types over time (e.g., a variable can hold a number at one point and a string at another).
There are three main ways to declare variables in JavaScript:
In general, modern JavaScript development recommends using let and const due to their block-scoping behavior, and var is considered outdated for most use cases.
The differences between var, let, and const mainly relate to scoping rules, hoisting behavior, and mutability:
In modern JavaScript, let and const are preferred over var due to their clearer and more predictable scoping rules.
In JavaScript, both undefined and null are primitive values that represent the absence of a value, but they are used in different contexts and have distinct characteristics:
undefined:
A variable is declared but not initialized:
Total width = width + left padding + right padding + left border + right border + left margin + right margin
Total height = height + top padding + bottom padding + top border + bottom border + top margin + bottom margin
To avoid this "extra" space being included in the element's dimensions, you can use the box-sizing: border-box; property. This makes padding and borders part of the element's specified width and height.
The display property in CSS determines how an element behaves in the layout of a page. The three most common display types are:
Example:
div {
display: block;
width: 100%;
}
Example:
span {
display: inline;
}
Example:
.box {
display: inline-block;
width: 100px;
height: 100px;
}
The z-index property in CSS controls the stacking order of positioned elements (those with position: relative, position: absolute, or position: fixed). It determines which elements appear in front of or behind other elements when they overlap. Elements with higher z-index values will appear on top of elements with lower z-index values.
Example:
.modal {
position: absolute;
z-index: 100;
}
.overlay {
position: absolute;
z-index: 50;
}
Here, the .modal will be displayed above the .overlay because its z-index is higher.
Note that z-index only works with elements that have a positioning context (i.e., elements with position set to something other than static).
The float property in CSS is used to position elements (usually images or text) to the left or right of their container, allowing content to wrap around them. It's primarily used for creating layouts, especially in older designs, but it has largely been replaced by more modern layout techniques like Flexbox and Grid.
Example:
img {
float: left;
margin-right: 10px;
}
Function Expression: A function can also be defined as an expression and assigned to a variable. This is an anonymous function (it doesn’t have a name) or a named function expression.
const add = function(x, y) {
return x + y;
};
console.log(add(2, 3)); // 5
Arrow Function (ES6): Introduced in ECMAScript 6, arrow functions provide a more concise syntax and are especially useful for writing short functions. Arrow functions also have different scoping behavior for the this keyword, which makes them ideal for some use cases like event handling.
const multiply = (a, b) => a * b;
console.log(multiply(4, 5)); // 20
Anonymous Function: Functions that don’t have a name, often used in callbacks or higher-order functions:
setTimeout(function() {
console.log("This will run after 1 second.");
}, 1000);
Function Parameters and Return Values:
You can call a function by passing arguments—the actual values that correspond to the parameters.
function subtract(a, b) {
return a - b;
}
console.log(subtract(10, 5)); // 5
JavaScript functions are first-class citizens, meaning they can be passed around as arguments, returned from other functions, and assigned to variables. This is a powerful feature of JavaScript.
In the context of JavaScript functions:
Parameter: A parameter is a variable that is used in the function definition to accept values when the function is called. Parameters act as placeholders for the actual values that will be passed to the function. Parameters are declared in the parentheses of a function declaration or function expression. Example:
function greet(name) { // "name" is a parameter
console.log("Hello, " + name);
}
Argument: An argument is the actual value or expression that is passed into the function when it is invoked (called). Arguments are passed to the function in place of the parameters defined in the function's signature. The number and order of arguments must match the parameters. Example:
greet("Alice"); // "Alice" is an argument passed to the "name" parameter
Key Differences:
Example of default parameters:
function multiply(a, b = 2) { // Default value for b is 2
return a * b;
}
console.log(multiply(3)); // 6, since b defaults to 2
In JavaScript, both == (loose equality) and === (strict equality) are comparison operators used to check if two values are equal, but they differ significantly in how they perform comparisons:
== (Loose Equality):
Example:
console.log(5 == "5"); // true, because "5" is coerced into the number 5
console.log(null == undefined); // true, because null and undefined are considered equal
Pitfall: Because == converts values to the same type, it can sometimes lead to unexpected results.
console.log(0 == false); // true, because both are considered falsy values
console.log('' == false); // true, because an empty string is coerced to false
=== (Strict Equality):
Example:
console.log(5 === "5"); // false, because one is a number and the other is a string
console.log(null === undefined); // false, because they are of different types
In JavaScript, objects are used to store collections of data and more complex entities. Objects consist of properties (key-value pairs) and can also have methods (functions that belong to the object).
Ways to Create Objects:
Object Literal: The most common and simple way to create an object is by using object literals. This is done by enclosing a list of properties and methods inside curly braces {}.
const person = {
firstName: "John",
lastName: "Doe",
age: 30,
greet: function() {
console.log("Hello, " + this.firstName);
}
};
person.greet(); // "Hello, John"
Using the new Object() Syntax: You can also create an object using the new Object() syntax, though this is less common and often seen as more verbose than using literals.
const person = new Object();
person.firstName = "John";
person.lastName = "Doe";
person.age = 30;
person.greet = function() {
console.log("Hello, " + this.firstName);
};
Using a Constructor Function: Another way to create an object is by defining a constructor function. Constructor functions allow you to create multiple instances of similar objects.
function Person(firstName, lastName, age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.greet = function() {
console.log("Hello, " + this.firstName);
};
}
const person1 = new Person("Alice", "Smith", 25);
const person2 = new Person("Bob", "Johnson", 28);
person1.greet(); // "Hello, Alice"
Using Object.create(): The Object.create() method creates a new object, using an existing object as the prototype.
const personProto = {
greet: function() {
console.log("Hello, " + this.firstName);
}
};
const person = Object.create(personProto);
person.firstName = "Eve";
person.lastName = "Green";
person.greet(); // "Hello, Eve"
Key Characteristics of JavaScript Objects:
A JavaScript array is a data structure used to store a collection of elements, typically of the same type (though JavaScript arrays are flexible and can hold mixed data types). Arrays are ordered, meaning the items are indexed and can be accessed by their position (index) in the array. JavaScript arrays can store any type of data, including numbers, strings, objects, and even other arrays (i.e., multidimensional arrays).
Creating an Array:
Array Literal: The most common and easiest way to create an array is using the array literal syntax, where you define the elements inside square brackets [].
let fruits = ['apple', 'banana', 'cherry'];
console.log(fruits); // ["apple", "banana", "cherry"]
When passing a single numeric argument, it creates an array with that many empty slots:
let emptyArray = new Array(5); // Array of 5 empty slots
console.log(emptyArray); // [ <5 empty items> ]
When passing multiple arguments, it creates an array with those elements:
let colors = new Array('red', 'green', 'blue');
console.log(colors); // ["red", "green", "blue"]
Using the Array.of() Method: This method is used to create an array from a list of elements, regardless of the type or quantity of arguments.
let numbers = Array.of(1, 2, 3);
console.log(numbers); // [1, 2, 3]
Using the Array.from() Method: Array.from() creates a new array from an array-like or iterable object, such as a string or the arguments object.
let str = "hello";
let arr = Array.from(str);
console.log(arr); // ["h", "e", "l", "l", "o"]
Accessing Array Elements:
Array elements can be accessed using zero-based indexing:
console.log(fruits[0]); // "apple"
console.log(fruits[2]); // "cherry"
JavaScript provides multiple ways to iterate over arrays, each suited to different use cases:
Using a for Loop: A traditional for loop is one of the most common ways to iterate over an array when you need control over the loop index.
let fruits = ['apple', 'banana', 'cherry'];
for (let i = 0; i < fruits.length; i++) {
console.log(fruits[i]); // "apple", "banana", "cherry"
}
Using forEach(): The forEach() method is an array method that executes a provided function once for each element in the array. It is a cleaner alternative to a for loop.
fruits.forEach(function(fruit) {
console.log(fruit); // "apple", "banana", "cherry"
});
Using map(): The map() method creates a new array populated with the results of calling a provided function on every element in the array.
let uppercaseFruits = fruits.map(function(fruit) {
return fruit.toUpperCase();
});
console.log(uppercaseFruits); // ["APPLE", "BANANA", "CHERRY"]
Using for...of Loop: The for...of loop is a more modern and concise way to iterate over arrays (and other iterable objects like strings or sets).
for (let fruit of fruits) {
console.log(fruit); // "apple", "banana", "cherry"
}
Using for...in Loop: The for...in loop iterates over the enumerable properties (indices) of an object, including arrays. However, it’s generally not recommended for arrays due to potential issues with inherited properties.
for (let index in fruits) {
console.log(fruits[index]); // "apple", "banana", "cherry"
}
Using reduce(): The reduce() method applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value.
let sum = [1, 2, 3].reduce(function(acc, num) {
return acc + num;
}, 0);
console.log(sum); // 6
In JavaScript, an event is an action or occurrence that happens in the browser, such as a user interaction (click, hover, keypress), or a change in the environment (like a page load). JavaScript allows you to listen for these events and respond to them with code.
Example:
<button id="clickMe">Click Me</button>
<script>
// Attach an event listener to the button
document.getElementById('clickMe').addEventListener('click', function() {
alert("Button was clicked!");
});
</script>
Common Event Types:
A callback function is a function passed as an argument to another function and is executed after the completion of that function’s operation. This allows for asynchronous programming and is commonly used in handling events, asynchronous operations, and handling results after a task is completed.
Example:
function greetUser(name, callback) {
console.log("Hello, " + name);
callback();
}
function showMessage() {
console.log("Welcome to JavaScript!");
}
greetUser("Alice", showMessage);
// Output:
// Hello, Alice
// Welcome to JavaScript!
In this example, showMessage is a callback function passed to greetUser. Once greetUser finishes executing, it calls the showMessage function.
Asynchronous Callbacks:
Callbacks are often used with asynchronous code, such as setTimeout, event listeners, and network requests:
setTimeout(function() {
console.log("This message appears after 2 seconds.");
}, 2000);
An anonymous function is a function that does not have a name. It’s typically used when you need a short-lived function for a one-time use, such as in callbacks, event handlers, or higher-order functions.
Example:
setTimeout(function() {
console.log("This is an anonymous function!");
}, 1000);
In this example, the function inside setTimeout is an anonymous function because it doesn’t have a name. It’s directly passed as an argument.
Arrow Function (ES6):
Arrow functions are a concise syntax for writing anonymous functions:
setTimeout(() => {
console.log("This is an arrow function!");
}, 1000);
Scope in JavaScript refers to the accessibility of variables and functions in different parts of the code. There are several types of scope in JavaScript:
let globalVar = "I am global!";
function showVar() {
console.log(globalVar); // "I am global!"
}
showVar();
function example() {
let localVar = "I am local!";
console.log(localVar); // "I am local!"
}
example();
console.log(localVar); // ReferenceError: localVar is not defined
if (true) {
let blockVar = "I am block-scoped";
console.log(blockVar); // "I am block-scoped"
}
console.log(blockVar); // ReferenceError: blockVar is not defined
Lexical Scope:
JavaScript uses lexical scoping, meaning the scope is determined by the location where a function is defined, not where it is called.
The global object is the highest-level object in the JavaScript environment. It serves as a container for all globally accessible variables and functions. Its properties and methods are available globally in your JavaScript code.
In browsers, the global object is window. Any variable declared in the global scope becomes a property of the window object.
let globalVar = "I am global!";
console.log(window.globalVar); // "I am global!"
In Node.js, the global object is global. In Node.js, variables declared in the global scope are accessible via the global object.
globalVar = "I am global!";
console.log(global.globalVar); // "I am global!"
The this keyword in JavaScript refers to the current execution context of a function. It refers to the object that is executing the current piece of code. The value of this depends on how a function is called:
In a regular function: this refers to the global object (in browsers, it's the window object), unless in strict mode, where it would be undefined.
function myFunction() {
console.log(this); // In non-strict mode, 'this' is the global object (window in browsers)
}
myFunction();
In an object method: this refers to the object that owns the method.
const person = {
name: "Alice",
greet: function() {
console.log("Hello, " + this.name);
}
};
person.greet(); // "Hello, Alice"
In a constructor function: this refers to the newly created object.
function Person(name) {
this.name = name;
}
const john = new Person("John");
console.log(john.name); // "John"
In arrow functions: this does not refer to the object that invokes the function, but instead is lexically bound, meaning it inherits the value of this from its surrounding scope.
const obj = {
name: "Alice",
greet: () => {
console.log(this.name); // 'this' refers to the global object (window in browsers)
}
};
obj.greet(); // undefined (in browsers, 'this' is the global object)
In synchronous programming, operations are executed one after the other. Each operation must complete before the next one begins, blocking further execution until the current operation finishes.
Synchronous Example:
console.log("Start");
console.log("End");
// Output:
// Start
// End
In asynchronous programming, operations can run independently of the main program flow. When an operation is asynchronous, the program doesn’t wait for it to complete; instead, it moves on to the next task. Once the asynchronous task completes, a callback or promise is used to handle the result.
Asynchronous Example:
console.log("Start");
setTimeout(() => {
console.log("Middle");
}, 1000);
console.log("End");
// Output:
// Start
// End
// Middle (after 1 second)
A Promise in JavaScript is an object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. It allows you to write asynchronous code in a more readable and manageable way, avoiding "callback hell."
States of a Promise:
Creating and Using a Promise:
let myPromise = new Promise((resolve, reject) => {
let success = true;
if (success) {
resolve("Task completed successfully!");
} else {
reject("Task failed.");
}
});
myPromise
.then((result) => {
console.log(result); // "Task completed successfully!"
})
.catch((error) => {
console.log(error); // If the promise is rejected
});
Promises are commonly used in asynchronous operations like handling network requests, reading files, or performing timeouts, and they can be chained together for better readability.
The event loop is a fundamental concept in JavaScript, especially in the context of asynchronous programming. It allows JavaScript to perform non-blocking operations by handling multiple tasks concurrently, even though JavaScript itself is single-threaded.
How the event loop works:
Example:
console.log("Start");
setTimeout(() => {
console.log("This is from setTimeout");
}, 0);
console.log("End");
// Output:
// Start
// End
// This is from setTimeout
In this example, the setTimeout callback is placed in the callback queue even though it's set to 0 milliseconds. The event loop only executes it after the main call stack is empty (after "Start" and "End" are logged).
Both setTimeout() and setInterval() are functions in JavaScript that handle timing operations, often used in asynchronous tasks.
Syntax:
setTimeout(callback, delay);
Example:
setTimeout(() => {
console.log("This message appears after 2 seconds.");
}, 2000);
Example:
let count = 0;
let intervalId = setInterval(() => {
console.log("This message appears every 2 seconds.");
count++;
if (count >= 3) clearInterval(intervalId); // Stop after 3 times
}, 2000);
Template literals (introduced in ES6) are a way to embed expressions inside string literals, making string concatenation easier and more readable. They are enclosed by backticks (`) rather than single (') or double quotes (").
Features of Template Literals:
Expression Interpolation: You can embed variables or expressions inside a string using ${} syntax.Example:
let name = "Alice";
let age = 25;
let message = `Hello, my name is ${name} and I am ${age} years old.`;
console.log(message); // "Hello, my name is Alice and I am 25 years old."
Multiline Strings: Template literals can span multiple lines without the need for concatenation or escape characters.Example:
let greeting = `Hello,
How are you today?`;
console.log(greeting);
Tagged Templates: A more advanced feature where you can define a function to manipulate the template literal.Example:
function tag(strings, ...values) {
console.log(strings); // Array of string literals
console.log(values); // Array of interpolated values
}
let name = "Bob";
let age = 30;
tag`Hello, my name is ${name} and I am ${age} years old.`;
A closure is a function that "remembers" its lexical scope (the variables from its surrounding context) even when the function is executed outside of that scope. In other words, closures allow functions to retain access to variables in their defining scope after the outer function has finished execution.
Key Characteristics:
Example:
function outerFunction() {
let counter = 0; // `counter` is enclosed by the closure
return function innerFunction() {
counter++;
console.log(counter);
};
}
const increment = outerFunction(); // `increment` is a closure
increment(); // 1
increment(); // 2
In this example, innerFunction retains access to the counter variable even after outerFunction has finished execution. This is because innerFunction is a closure.
A higher-order function is a function that:
Higher-order functions are a powerful concept in functional programming and are commonly used for operations like map, filter, and reduce.
Example:
function applyOperation(a, b, operation) {
return operation(a, b);
}
function add(x, y) {
return x + y;
}
console.log(applyOperation(5, 3, add)); // 8
In this example, applyOperation is a higher-order function because it takes a function (add) as an argument and uses it inside its body.
The typeof operator in JavaScript is used to determine the type of a given operand. It returns a string that indicates the type of the variable or expression.
Example:
console.log(typeof "Hello"); // "string"
console.log(typeof 42); // "number"
console.log(typeof true); // "boolean"
console.log(typeof {}); // "object"
console.log(typeof []); // "object" (arrays are objects in JavaScript)
console.log(typeof null); // "object" (this is a known quirk in JavaScript)
console.log(typeof undefined); // "undefined"
console.log(typeof function() {}); // "function"
The typeof operator helps to check the type of a variable, but be cautious with special cases like null, which returns "object".
Both function declarations and function expressions are ways to define functions, but they differ in how and when they are hoisted and used.
Function Declaration: A function declaration defines a named function. It is hoisted, meaning the function definition is available throughout the entire scope, even before the function is declared in the code.Example:
console.log(add(2, 3)); // 5
function add(a, b) {
return a + b;
}
Function Expression: A function expression involves creating a function and assigning it to a variable. Function expressions are not hoisted, meaning you cannot call the function before it is defined.Example:
let add = function(a, b) {
return a + b;
};
console.log(add(2, 3)); // 5
Key Differences:
A default parameter allows a function to specify a default value for one or more of its parameters if no argument is provided. This feature was introduced in ES6.
Example:
function greet(name = "Guest") {
console.log("Hello, " + name);
}
greet(); // "Hello, Guest"
greet("Alice"); // "Hello, Alice"
In this example, if the greet() function is called without an argument, it uses the default value "Guest".
Default Parameters with Expressions:
You can also use expressions as default values:
function multiply(a, b = 2) {
return a * b;
}
console.log(multiply(5)); // 10 (uses default value for `b`)
console.log(multiply(5, 3)); // 15
Arrow functions (introduced in ES6) provide a concise syntax for writing functions. They are particularly useful for anonymous functions and are often used in higher-order functions like map, filter, or reduce. Arrow functions have different behavior for the this keyword, making them easier to use in certain contexts.
const add = (a, b) => a + b;
console.log(add(2, 3)); // 5
Key Features:
Example:
const person = {
name: "Alice",
greet: function() {
setTimeout(() => {
console.log(`Hello, ${this.name}`);
}, 1000);
}
};
person.greet(); // "Hello, Alice"
In this case, the arrow function inside setTimeout inherits the this value from the greet method, ensuring that it refers to the person object.
Both let and const are used to declare variables in JavaScript and are part of the block scope (introduced in ES6), but they differ in their behavior:
Example:
let x = 5;
x = 10; // Reassignable
console.log(x); // 10
Example:
const y = 5;
// y = 10; // Error: Assignment to constant variable.
const obj = { name: "Alice" };
obj.name = "Bob"; // Works, since the object itself is mutable
console.log(obj); // { name: "Bob" }
JavaScript modules are a way to break up large pieces of JavaScript code into smaller, reusable units. Each module can encapsulate functionality, making your codebase more maintainable, readable, and modular. Modules help manage dependencies and scope, improving the structure and organization of your application.
Key Features of JavaScript Modules:
Example:
Exporting from a module (in math.js):
// math.js
export function add(a, b) {
return a + b;
}
export const PI = 3.14159;
Importing in another file (in app.js):
// app.js
import { add, PI } from './math.js';
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159
Modern JavaScript (ES6) allows modules to be used with the import/export syntax, and older versions of JavaScript used CommonJS or AMD module formats. In browsers, you can use the <script type="module"> tag to load JavaScript modules.
The DOM (Document Object Model) is an interface provided by browsers that represents the structure of an HTML or XML document as a tree of nodes. Each node corresponds to a part of the document (e.g., an element, an attribute, or text content). JavaScript can interact with the DOM to manipulate the content, structure, and style of a web page dynamically.
Key Concepts:
Example:
// Accessing the DOM elements
let heading = document.getElementById("myHeading");
heading.textContent = "New Heading Text"; // Change the text of the heading
// Creating new elements and appending them
let newDiv = document.createElement("div");
newDiv.textContent = "Hello, this is a new div!";
document.body.appendChild(newDiv); // Add the new div to the page
The window object is the global object in a browser environment. It represents the browser's window and provides access to various properties and methods that control the browser's behavior, the DOM, and the environment where JavaScript runs.
Key Features of the window Object:
Example:
// Accessing properties of the window object
console.log(window.innerWidth); // Get the width of the window
window.alert("Hello, world!"); // Show an alert box
// Window location
console.log(window.location.href); // Get current URL
Event delegation is a technique in JavaScript where you attach a single event listener to a parent element instead of attaching individual event listeners to each child element. When an event occurs on a child element, it bubbles up to the parent, where it is handled. This allows for more efficient event handling, especially for dynamic content.
How it works:
Example:
// Event delegation example
document.getElementById("parent").addEventListener("click", function(event) {
if (event.target && event.target.matches("button")) {
console.log("Button clicked:", event.target.textContent);
}
});
// HTML structure
// <div id="parent">
// <button>Click Me</button>
// <button>Click Me Too</button>
// </div>
In this example, the event listener on the parent element listens for click events and checks if the clicked target is a button.
In JavaScript, errors can be handled using try-catch blocks, or by using error-handling mechanisms such as promises or event listeners.
Key Approaches:
try-catch block: Used for synchronous error handling. The code that may throw an error is placed inside the try block, and if an error occurs, it is caught in the catch block.Example:
try {
let result = riskyFunction(); // Function that might throw an error
console.log(result);
} catch (error) {
console.error("Error occurred:", error);
}
finally block: The finally block will execute whether or not an error occurred in the try block.Example:
try {
let result = riskyFunction();
} catch (error) {
console.error("Error:", error);
} finally {
console.log("Cleanup code runs regardless of success or failure.");
}
Promises: When working with asynchronous code, promises can be used with .catch() to handle errors.Example:
fetch("someapi.com")
.then(response => response.json())
.catch(error => console.error("Fetch error:", error));
The try-catch statement is used to handle exceptions (runtime errors) in JavaScript. It allows you to attempt to execute a block of code (try), and if an error occurs, it will be caught in the catch block, preventing the script from crashing.
Syntax:
try {
// Code that may throw an error
} catch (error) {
// Code to handle the error
}
Example:
try {
let number = 10;
let result = number / 0; // This will not throw an error, but just return Infinity
console.log(result);
} catch (error) {
console.log("An error occurred:", error);
}
The catch block provides access to the error object, which can be used for debugging purposes or to provide a custom error message.
The map() method in JavaScript is used to create a new array populated with the results of calling a provided function on every element in the calling array. The original array remains unchanged. This is part of array iteration methods that allow functional-style programming in JavaScript.
Syntax:
let newArray = array.map(function(element, index, array) {
// return transformed element
});
Example:
let numbers = [1, 2, 3, 4, 5];
let squaredNumbers = numbers.map(num => num * num);
console.log(squaredNumbers); // [1, 4, 9, 16, 25]
In this example, map() creates a new array with each number squared, without modifying the original numbers array.
In JavaScript, creating a shallow copy of an array means creating a new array where the elements of the original array are copied, but the subarrays (if any) or objects inside the array are still references to the original ones. A shallow copy is different from a deep copy, where the entire structure (including nested objects) is copied recursively.
Methods to create a shallow copy:
Using the slice() method:
let arr = [1, 2, 3];
let copy = arr.slice();
console.log(copy); // [1, 2, 3]
Using the spread operator (...):
let arr = [1, 2, 3];
let copy = [...arr];
console.log(copy); // [1, 2, 3]
Using Array.from():
let arr = [1, 2, 3];
let copy = Array.from(arr);
console.log(copy); // [1, 2, 3]
In each case, a shallow copy is created, and changes to the original array or its elements will not affect the shallow copy (unless they are objects or arrays themselves).
Both localStorage and sessionStorage are part of the Web Storage API and allow storing data on the client's browser, but they differ in their lifespan and scope.
Example:
localStorage.setItem('username', 'Alice');
let name = localStorage.getItem('username');
console.log(name); // "Alice"
Example:
sessionStorage.setItem('username', 'Bob');
let name = sessionStorage.getItem('username');
console.log(name); // "Bob"
In JavaScript, classes are a template for creating objects with shared properties and methods. Classes were introduced in ES6 as syntactic sugar over the existing prototype-based inheritance model.
Syntax:
class ClassName {
constructor(parameter1, parameter2) {
this.property1 = parameter1;
this.property2 = parameter2;
}
method1() {
console.log(this.property1);
}
static staticMethod() {
console.log("This is a static method");
}
}
Example:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
static species() {
console.log("Humans");
}
}
let person1 = new Person("Alice", 30);
person1.greet(); // "Hello, my name is Alice and I am 30 years old."
Person.species(); // "Humans" (static method)
Classes in JavaScript support inheritance using the extends keyword, allowing one class to inherit properties and methods from another class.
Hoisting is a JavaScript mechanism where variable and function declarations are moved to the top of their containing scope during the compilation phase, before the code is executed. This means that functions and variables can be used before they are formally declared in the code.
How hoisting works:
Examples:
Function Hoisting:
hoistedFunction(); // Works because function declarations are hoisted
function hoistedFunction() {
console.log("I am hoisted!");
}
Variable Hoisting:
console.log(myVar); // undefined, not ReferenceError, because `myVar` is hoisted but not initialized
var myVar = 5;
console.log(myVar); // 5
Let and Const Hoisting:
console.log(myLet); // ReferenceError: Cannot access 'myLet' before initialization
let myLet = 10;
In summary, hoisting allows variables and functions to be used before their actual declaration, but with subtle differences depending on how they are declared (var, let, const, function).
These three methods are used to explicitly set the value of the this keyword within a function. They allow you to control the context in which the function is executed.
Syntax:
const boundFunction = originalFunction.bind(thisArg);
Example:
function greet() {
console.log(`Hello, ${this.name}`);
}
const person = { name: 'Alice' };
const greetAlice = greet.bind(person);
greetAlice(); // Hello, Alice
Syntax:
originalFunction.call(thisArg, arg1, arg2, ...);
Example:
function greet(age) {
console.log(`Hello, ${this.name}. You are ${age} years old.`);
}
const person = { name: 'Bob' };
greet.call(person, 25); // Hello, Bob. You are 25 years old.
Syntax:
originalFunction.apply(thisArg, [argsArray]);
Example:
function greet(age, occupation) {
console.log(`Hello, ${this.name}. You are ${age} years old and work as a ${occupation}.`);
}
const person = { name: 'Charlie' };
greet.apply(person, [30, 'developer']); // Hello, Charlie. You are 30 years old and work as a developer.
null and undefined are both primitive values in JavaScript, but they represent different concepts:
Example:
let x;
console.log(x); // undefined (x is declared but not initialized)
function noReturn() {}
console.log(noReturn()); // undefined (no return value)
Example:
let person = null; // null is explicitly assigned
console.log(person); // null
Key Difference:
In JavaScript, the scope defines the accessibility of variables, functions, and objects in different parts of the code. JavaScript has two types of scoping:
Example:
function example() {
var a = 5; // function-scoped
console.log(a); // Works inside the function
}
console.log(a); // Error: a is not defined (if accessed outside)
Example:
if (true) {
let x = 10; // block-scoped
const y = 20; // block-scoped
console.log(x, y); // 10, 20
}
console.log(x, y); // Error: x is not defined (or y is not defined
There are several ways to clone an object in JavaScript. These methods vary based on whether you want to create a shallow copy or a deep copy.
let original = { name: "Alice", address: { city: "Wonderland" } };
let clone = Object.assign({}, original);
console.log(clone); // { name: "Alice", address: { city: "Wonderland" } }
let original = { name: "Bob", address: { city: "Gotham" } };
let clone = { ...original };
console.log(clone); // { name: "Bob", address: { city: "Gotham" } }
let original = { name: "Charlie", address: { city: "Metropolis" } };
let deepClone = JSON.parse(JSON.stringify(original));
console.log(deepClone); // { name: "Charlie", address: { city: "Metropolis" } }
A promise chain is a sequence of .then() or .catch() methods that are chained together to handle multiple asynchronous operations in sequence. Each .then() method returns a new promise, allowing you to chain further operations.
How it works:
Example:
fetch('https://api.example.com')
.then(response => response.json()) // First .then - handles response
.then(data => {
console.log(data); // Second .then - handles the data
})
.catch(error => {
console.log('Error:', error); // .catch - handles any error
});
Advantages of Promises:
Disadvantages of Promises:
Example:
async function fetchData() {
let response = await fetch('https://api.example.com');
let data = await response.json();
console.log(data);
}
fetchData();
A closure is a function that "remembers" its lexical environment, even when it's executed outside of its original scope. This means that a closure can access variables from its containing function even after the containing function has finished execution.
Example:
function outer() {
let count = 0;
return function inner() {
count++;
console.log(count);
}
}
const counter = outer();
counter(); // 1
counter(); // 2
In this example, the inner function is a closure that has access to the count variable from the outer function, even though outer() has already returned.
In JavaScript, the this keyword refers to the context in which a function is called. It behaves differently in various scenarios:
Global Context: In the global execution context (outside any function), this refers to the global object (window in browsers).
console.log(this); // window (in a browser)
Object Method: When a function is called as a method of an object, this refers to the object itself.
const person = {
name: 'Alice',
greet: function() {
console.log(this.name); // this refers to the person object
}
};
person.greet(); // Alice
Function Context: In a regular function call, this refers to the global object in non-strict mode (window in browsers), and undefined in strict mode.
function show() {
console.log(this); // window (in non-strict mode)
}
show();
Arrow Functions: Arrow functions do not have their own this; they inherit this from their surrounding context.
const person = {
name: 'Bob',
greet: function() {
setTimeout(() => {
console.log(this.name); // this refers to the person object, because of lexical scoping
}, 1000);
}
};
person.greet(); // Bob
In summary, this refers to the context in which a function is executed, and its behavior depends on whether the function is in the global scope, part of an object, or inside a constructor or an arrow function.
The Object.create() method is used to create a new object with a specified prototype object and optional properties. It's a powerful tool for working with inheritance in JavaScript, as it allows you to set up an object's prototype chain explicitly.
Syntax:
Object.create(proto, propertiesObject);
Use Cases:
Creating an object with a custom prototype:
const animal = {
speak: function() {
console.log('Animal sound');
}
};
const dog = Object.create(animal);
dog.speak(); // "Animal sound" (dog inherits speak from animal)
A singleton is a design pattern that ensures a class or object has only one instance, while providing a global access point to that instance. This pattern is often used when you want to manage a resource, like a configuration or a connection pool, and ensure there is only one instance managing that resource.
Example:
const Singleton = (function() {
let instance;
function createInstance() {
return { value: 'I am the only instance' };
}
return {
getInstance: function() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
const singleton1 = Singleton.getInstance();
const singleton2 = Singleton.getInstance();
console.log(singleton1 === singleton2); // true
In this example, Singleton.getInstance() ensures that only one instance of the object is created, regardless of how many times it is called.
JavaScript modules allow you to break up your code into smaller, reusable pieces, making it easier to manage and maintain. Modules provide a way to export and import functionality between different files.
How to use JavaScript modules:
Exporting: You export functions, objects, or variables to make them available for import in other files.
// math.js
export function add(a, b) {
return a + b;
}
export const pi = 3.14;
Importing: You import the exported entities from other modules.
// app.js
import { add, pi } from './math.js';
console.log(add(2, 3)); // 5
console.log(pi); // 3.14
Default Export: You can export a single value as the default export of a module.
// logger.js
export default function log(message) {
console.log(message);
}
// app.js
import log from './logger.js';
log("This is a log message");
JavaScript modules were introduced in ES6 and are now supported in modern browsers and Node.js. They allow for better code organization and modularity.
Destructuring is a syntax feature in JavaScript that allows you to unpack values from arrays or properties from objects into distinct variables. It's a concise and readable way to assign variables from complex data structures.
Array Destructuring:
const arr = [1, 2, 3, 4];
const [first, second, third] = arr;
console.log(first, second, third); // 1, 2, 3
Object Destructuring:
const person = { name: 'Alice', age: 25 };
const { name, age } = person;
console.log(name, age); // Alice, 25
You can also use default values, rest syntax, and rename variables when destructuring.
Example with default values and renaming:
const person = { name: 'Bob' };
const { name, age = 30 } = person;
console.log(name, age); // Bob, 30
The spread operator (...) allows an iterable (like an array or object) to be expanded into individual elements or properties. It's often used to clone objects or arrays, combine multiple arrays, or pass individual arguments to functions.
Examples:
Expanding an array:
const numbers = [1, 2, 3];
const newNumbers = [...numbers, 4, 5];
console.log(newNumbers); // [1, 2, 3, 4, 5]
Merging arrays:
const arr1 = [1, 2];
const arr2 = [3, 4];
const mergedArr = [...arr1, ...arr2];
console.log(mergedArr); // [1, 2, 3, 4]
Cloning an object:
const person = { name: 'Alice', age: 25 };
const clonedPerson = { ...person };
console.log(clonedPerson); // { name: 'Alice', age: 25 }
Using with function arguments:
function greet(name, age) {
console.log(`Hello ${name}, you are ${age} years old.`);
}
const args = ['Alice', 25];
greet(...args); // Hello Alice, you are 25 years old.
The rest parameter (...) allows you to collect multiple arguments into a single array parameter. It is used in function definitions to handle variable numbers of arguments.
Example:
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(4, 5, 6, 7)); // 22
In this case, numbers is an array containing all the arguments passed to the function. The rest parameter can only be the last parameter in a function.
A shallow copy creates a new object or array, but the elements inside it (if they are references to objects or arrays) are still pointing to the same memory locations as the original object. Changes to the nested objects will reflect in both the original and the copied object.
A deep copy creates a new object or array and recursively copies all nested objects or arrays, ensuring that the copied object is completely independent of the original.
Shallow Copy Example:
let original = { name: 'Alice', details: { age: 25 } };
let shallowCopy = { ...original };
shallowCopy.details.age = 26;
console.log(original.details.age); // 26 (shallow copy affects original)
Deep Copy Example:
let original = { name: 'Alice', details: { age: 25 } };
let deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.details.age = 26;
console.log(original.details.age); // 25 (deep copy does not affect original)
In JavaScript, errors in asynchronous code can be handled using callbacks, promises, or async/await:
Using Callbacks:
function fetchData(callback) {
setTimeout(() => {
try {
throw new Error('Data fetch error');
} catch (error) {
callback(error, null);
}
}, 1000);
}
fetchData((error, data) => {
if (error) {
console.log('Error:', error);
} else {
console.log('Data:', data);
}
});
Using Promises:
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('Data fetch error'));
}, 1000);
});
}
fetchData()
.then(data => console.log(data))
.catch(error => console.log('Error:', error));
Using Async/Await:
async function fetchData() {
try {
throw new Error('Data fetch error');
} catch (error) {
console.log('Error:', error);
}
}
fetchData();
Asynchronous error handling allows you to handle failures gracefully in asynchronous code and ensures that your program doesn't crash unexpectedly.
The EventEmitter class in Node.js is part of the events module and is used to handle and manage events in an application. It allows you to create custom events and register listeners that are triggered when the event is emitted.
Usage:
Creating an EventEmitter instance:
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
Emitting an event:
myEmitter.emit('event', 'some data');
Listening to an event:
myEmitter.on('event', (data) => {
console.log('An event occurred with data:', data);
});
Example:
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
myEmitter.on('greet', (name) => {
console.log(`Hello, ${name}!`);
});
myEmitter.emit('greet', 'Alice'); // Hello, Alice!
This pattern is widely used in Node.js for event-driven programming, especially when dealing with streams, I/O operations, and server requests.
The setInterval() function is used to repeatedly execute a function at specified intervals (in milliseconds). It returns a unique identifier that can be used to cancel the interval using clearInterval().
Syntax:
let intervalId = setInterval(function, delay);
Example:
let count = 0;
let intervalId = setInterval(() => {
count++;
console.log(count);
if (count === 5) {
clearInterval(intervalId); // Stop the interval after 5 counts
}
}, 1000); // Executes every 1000ms (1 second)
In this example, the function logs the value of count every second, and it stops after logging the number 5. clearInterval() is used to stop the interval.
Template literals are a feature introduced in ES6 that allow for string interpolation, multi-line strings, and embedded expressions within string literals. They are defined using backticks (`\) instead of single or double quotes.
Advantages of Template Literals:
String Interpolation: You can easily embed variables and expressions inside strings.
const name = 'Alice';
const greeting = `Hello, ${name}!`; // Interpolation
console.log(greeting); // Hello, Alice!
Multi-line Strings: Template literals preserve line breaks and indentation.
const multiLineString = `
This is line 1
This is line 2
This is line 3
`;
console.log(multiLineString);
Expression Embedding: You can embed any valid JavaScript expression inside ${}.
const a = 5, b = 10;
const result = `Sum of a and b is: ${a + b}`; // Expression inside template literal
console.log(result); // Sum of a and b is: 15
Template literals make string handling more concise, readable, and flexible compared to traditional string concatenation.
In JavaScript, == and === are comparison operators, but they differ in how they compare values:
Example:
console.log(5 == '5'); // true (type coercion, '5' is converted to a number)
console.log(true == 1); // true (true is coerced to 1)
Example:
console.log(5 === '5'); // false (different types, number vs. string)
console.log(true === 1); // false (boolean vs. number)
In general, it's recommended to use === to avoid unexpected behavior due to type coercion.
A closure is a function that has access to its own scope, the scope of the outer function, and the global scope. Closures are a powerful feature in JavaScript and are primarily used in the following cases:
function counter() {
let count = 0;
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
}
};
}
const myCounter = counter();
console.log(myCounter.increment()); // 1
console.log(myCounter.getCount()); // 1
function makeCounter() {
let count = 0;
return function() {
count++;
console.log(count);
};
}
const counter1 = makeCounter();
counter1(); // 1
counter1(); // 2
function multiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = multiplier(2);
console.log(double(5)); // 10
Closures allow for powerful patterns such as private data and function factories, making code more modular and maintainable.
A promise in JavaScript represents the eventual completion (or failure) of an asynchronous operation and its resulting value. You can create a promise using the new Promise() constructor.
Syntax:
const myPromise = new Promise((resolve, reject) => {
// Asynchronous operation
let success = true;
if (success) {
resolve('Operation successful!');
} else {
reject('Operation failed!');
}
});
Example:
const myPromise = new Promise((resolve, reject) => {
let success = true;
if (success) {
resolve('Task completed!');
} else {
reject('Task failed!');
}
});
myPromise
.then((message) => console.log(message)) // Success handler
.catch((error) => console.log(error)); // Error handler
In this example, the promise is resolved (i.e., successful) if the success variable is true, and it is rejected otherwise.
The reduce() method is used to reduce an array to a single value by executing a provided function on each element, accumulating the result.
Syntax:
arr.reduce(callback(accumulator, currentValue[, index, array]), initialValue);
Example:
const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((acc, num) => acc + num, 0);
console.log(sum); // 10 (1 + 2 + 3 + 4)
In this example, reduce() accumulates the sum of the numbers in the array.
In JavaScript, event propagation occurs in two phases: bubbling and capturing (also known as trickling).
Example:
document.getElementById('parent').addEventListener('click', () => {
console.log('Parent clicked');
});
document.getElementById('child').addEventListener('click', () => {
console.log('Child clicked');
});
// When clicking the child element, the log will show:
// "Child clicked"
// "Parent clicked" (event bubbles from child to parent)
Example (set capturing to true):
document.getElementById('parent').addEventListener('click', () => {
console.log('Parent clicked');
}, true); // true enables capturing
document.getElementById('child').addEventListener('click', () => {
console.log('Child clicked');
});
// When clicking the child element, the log will show:
// "Parent clicked"
// "Child clicked" (event captures from parent to child)
console.log('First');
console.log('Second'); // This will be executed only after the first log
setTimeout(() => {
console.log('Second'); // This will be executed after 1000ms
}, 1000);
console.log('First');
async and await are syntactic sugar built on top of promises that make handling asynchronous code look and behave more like synchronous code, making it easier to read and manage.
Example:
async function fetchData() {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const data = await response.json();
console.log(data);
}
fetchData();
In this example, await pauses the function until the fetch request resolves, making the code more readable than using then() with promises.
The setImmediate() function in Node.js is used to schedule a callback to be executed in the next iteration of the event loop (after the current event loop cycle completes). It’s typically used for deferring execution of a function to ensure it runs after the I/O events and timers.
Example:
console.log('Start');
setImmediate(() => {
console.log('Immediate');
});
console.log('End');
Output:
Start
End
Immediate
In this example, setImmediate() runs the callback after the current event loop cycle, which is why "Immediate" is logged after "End".
You can prevent the default behavior of an event using the preventDefault() method of the event object. This is commonly used when handling form submissions, anchor tags, or other events where you want to prevent the browser's default action.
Example:
document.getElementById('myForm').addEventListener('submit', function(event) {
event.preventDefault();
console.log('Form submission prevented!');
});
In this case, preventDefault() stops the form from being submitted when the user clicks the submit button.
In JavaScript, you can bind an event handler to an element in several ways. The most common methods are:
const button = document.getElementById('myButton');
button.addEventListener('click', function(event) {
alert('Button clicked!');
});
const button = document.getElementById('myButton');
button.onclick = function() {
alert('Button clicked!');
};
A JavaScript Proxy is a special object that allows you to define custom behavior for fundamental operations (like property lookup, assignment, enumeration, etc.) on an object. It can intercept and redefine operations for getting, setting, deleting properties, and more.
Creating a Proxy:
You create a proxy using the Proxy constructor, which takes two arguments:
Example:
const target = {
message: 'Hello, world!',
};
const handler = {
get: function(target, prop) {
if (prop in target) {
return target[prop];
} else {
return `Property ${prop} not found!`;
}
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.message); // "Hello, world!"
console.log(proxy.nonExistent); // "Property nonExistent not found!"
Uses of Proxy:
The call stack is a mechanism in JavaScript that keeps track of function calls. It follows the Last In, First Out (LIFO) principle, meaning that the most recently called function is executed first, and once it completes, it is popped off the stack.
How it works:
Example:
function first() {
second();
}
function second() {
third();
}
function third() {
console.log('End of stack');
}
first(); // Starts the chain of calls
// Call stack:
// third() -> second() -> first()
In this example, the function calls first(), which calls second(), which in turn calls third(). Once third() completes, it’s removed from the stack, then second(), and finally first().
Both bind() and call() are methods in JavaScript used to invoke functions with a specific this value, but they differ in their behavior:
function greet() {
console.log(`Hello, ${this.name}`);
}
const person = { name: 'Alice' };
const boundGreet = greet.bind(person);
boundGreet(); // "Hello, Alice"
function greet(city, country) {
console.log(`Hello, ${this.name} from ${city}, ${country}`);
}
const person = { name: 'Alice' };
greet.call(person, 'Paris', 'France'); // "Hello, Alice from Paris, France"
Key Differences:
A JavaScript generator is a function that can be paused and resumed, allowing you to produce a sequence of values on demand. They are defined using the function* syntax and use the yield keyword to pause execution.
Syntax:
function* myGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = myGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
console.log(gen.next().value); // undefined
In this example:
Generators are useful for implementing iterators, managing asynchronous code with yield (in the case of async/await), and working with large datasets lazily.
Memoization is an optimization technique where you store the results of expensive function calls and reuse them when the same inputs occur again. This can significantly improve performance, especially for functions with repetitive calculations (e.g., recursive algorithms).
Example:
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (key in cache) {
return cache[key];
} else {
const result = fn(...args);
cache[key] = result;
return result;
}
};
}
const factorial = memoize(function(n) {
if (n === 0) return 1;
return n * factorial(n - 1);
});
console.log(factorial(5)); // 120 (computed)
console.log(factorial(5)); // 120 (retrieved from cache)
In this example, the factorial function is memoized, so subsequent calls with the same argument return the cached result rather than recalculating it.
ES5 (ECMAScript 5) and ES6 (ECMAScript 6, also known as ECMAScript 2015) introduced significant changes and improvements to JavaScript. Key differences include:
// ES6
const add = (a, b) => a + b;
const name = 'Alice';
const greeting = `Hello, ${name}!`;
In JavaScript, you can create methods for an object by assigning functions as properties of the object.
Example 1: Method inside an object literal
const person = {
name: 'Alice',
greet: function() {
console.log(`Hello, my name is ${this.name}`);
}
};
person.greet(); // "Hello, my name is Alice"
Example 2: Using ES6 shorthand for methods
const person = {
name: 'Alice',
greet() {
console.log(`Hello, my name is ${this.name}`);
}
};
person.greet(); // "Hello, my name is Alice"
In both examples, greet is a method attached to the person object.
const obj = { name: 'Alice' };
Object.freeze(obj);
obj.name = 'Bob'; // Does nothing, name remains 'Alice'
const obj = { name: 'Alice' };
Object.seal(obj);
obj.name = 'Bob'; // Allowed, name is now 'Bob'
delete obj.name; // Does nothing, property cannot be deleted
Immutability refers to the concept that once a value is assigned to a variable or object, it cannot be changed. This is an important concept in JavaScript for several reasons:
Example:
const obj = Object.freeze({ name: 'Alice' });
obj.name = 'Bob'; // Throws an error or does nothing, depending on the environment
Using immutability, changes can be tracked more easily, and the program becomes less prone to bugs related to unexpected changes in state.
The event loop is a fundamental concept in JavaScript's concurrency model. JavaScript is single-threaded, meaning only one task can be executed at a time. The event loop ensures that non-blocking I/O operations (like timers, HTTP requests, or user interactions) are processed without freezing the application.
How it works:
Example:
console.log('Start');
setTimeout(function() {
console.log('Timeout');
}, 0);
console.log('End');
// Output:
// Start
// End
// Timeout
In this example, even though setTimeout is set to 0 milliseconds, the callback is pushed to the callback queue after the synchronous code completes, so "End" is logged before "Timeout".
A decorator in JavaScript is a function that can be used to modify or extend the behavior of another function or method. Decorators are often used to add functionality to an object, class, or method without directly altering their code.
Implementation (Method Decorator Example):
JavaScript doesn't have built-in support for decorators, but they can be implemented using higher-order functions. Decorators are common in frameworks like Angular.
Example:
// Basic decorator function
function logExecution(target, name, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
console.log(`Calling ${name} with arguments: ${args}`);
const result = originalMethod.apply(this, args);
console.log(`${name} returned: ${result}`);
return result;
};
return descriptor;
}
class MyClass {
@logExecution
add(a, b) {
return a + b;
}
}
const myClass = new MyClass();
myClass.add(2, 3); // Logs the execution details
In this example, the logExecution decorator logs the arguments and result of the method it decorates.
Optimizing JavaScript performance can involve various strategies:
A stack and a queue are both data structures, but they differ in how elements are added and removed:
Example:
const stack = [];
stack.push(1);
stack.push(2);
console.log(stack.pop()); // 2
console.log(stack.pop()); // 1
Example:
const queue = [];
queue.push(1);
queue.push(2);
console.log(queue.shift()); // 1
console.log(queue.shift()); // 2
The choice between stack and queue depends on how you need to process the data — stack for last-in-first-out scenarios (e.g., undo operations) and queue for first-in-first-out scenarios (e.g., task scheduling).
Memoization is a technique used to optimize performance by caching the results of expensive function calls and returning the cached result when the same inputs occur again. This can be implemented using a JavaScript object or Map.
Example:
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key); // Return cached result
}
const result = fn(...args);
cache.set(key, result); // Cache the result
return result;
};
}
// Expensive function
function factorial(n) {
if (n === 0) return 1;
return n * factorial(n - 1);
}
const memoizedFactorial = memoize(factorial);
console.log(memoizedFactorial(5)); // First time, computes and caches
console.log(memoizedFactorial(5)); // Second time, retrieves from cache
In this example, the memoize function caches the result of factorial() to avoid redundant calculations.
Example:
const weakMap = new WeakMap();
let obj = {};
weakMap.set(obj, 'value');
console.log(weakMap.get(obj)); // 'value'
obj = null; // Now the object is eligible for garbage collection
Example:
const weakSet = new WeakSet();
let obj = {};
weakSet.add(obj);
console.log(weakSet.has(obj)); // true
obj = null; // The object is eligible for garbage collection
A Symbol is a primitive data type in JavaScript introduced in ES6. It represents a unique and immutable identifier. Symbols are often used to add unique property keys to objects to avoid name clashes, especially in large applications or libraries.
Example:
const sym = Symbol('description');
const obj = {};
obj[sym] = 'value';
console.log(obj[sym]); // 'value'
Symbols are not enumerable by default, meaning they do not show up in for...in loops or Object.keys(). They are also used for defining well-known symbols like Symbol.iterator for custom iteration behavior.
Handling large-scale asynchronous code in JavaScript requires strategies to manage complexity, improve readability, and prevent callback hell. Some techniques include:
Promises: Use Promises for managing asynchronous operations and chaining multiple async operations in a readable manner.
fetchData()
.then(data => processData(data))
.then(result => displayResult(result))
.catch(error => handleError(error));
Async/Await: Use async/await syntax to write asynchronous code that looks synchronous, improving readability and reducing nested .then() chains.
async function fetchData() {
try {
const data = await fetchDataFromAPI();
const processedData = await processData(data);
displayResult(processedData);
} catch (error) {
handleError(error);
}
}
A polyfill is a piece of code (usually a library) that provides support for features not available in certain environments (like older browsers). Polyfills allow developers to use modern JavaScript features, such as Array.prototype.includes, Promise, or fetch(), even in environments that do not natively support them.
Example (Polyfill for Array.prototype.includes):
if (!Array.prototype.includes) {
Array.prototype.includes = function(element) {
return this.indexOf(element) !== -1;
};
}
You should use polyfills when:
Event delegation is a technique in JavaScript where you attach a single event listener to a parent element instead of multiple listeners to individual child elements. This is efficient because it takes advantage of event bubbling to capture events on child elements.
Example:
document.querySelector('#parent').addEventListener('click', function(event) {
if (event.target && event.target.matches('button.classname')) {
console.log('Button clicked!');
}
});
In this example, only one event listener is attached to the parent element #parent, and it handles clicks on any button.classname inside it.
Why it's important:
Both setImmediate() and setTimeout() are used to schedule asynchronous tasks in JavaScript, but they differ in when the tasks are executed:
Example:
setTimeout(() => console.log('Timeout'), 0);
console.log('Immediate');
// Output: "Immediate" first, then "Timeout"
Example:
setImmediate(() => console.log('Immediate'));
console.log('Timeout');
// Output: "Timeout" first, then "Immediate"
In summary, setImmediate() is specific to Node.js and gives a higher priority to I/O tasks over timers, whereas setTimeout() schedules a task to be run after a delay, and its callback is pushed into the task queue.
Prototypal inheritance is a feature in JavaScript where objects can inherit properties and methods from other objects. Every object in JavaScript has a prototype, which is another object that it inherits from. This allows for shared properties and methods among multiple instances of objects, enabling code reuse.
Example:
const animal = {
makeSound() {
console.log('Animal sound');
}
};
const dog = Object.create(animal); // dog inherits from animal
dog.makeSound(); // 'Animal sound'
In this example, dog does not have its own makeSound method, but it inherits it from the animal object’s prototype. This is the essence of prototypal inheritance.
While ES6 classes introduce a more familiar and syntactically cleaner way to define objects and inheritance, JavaScript still uses prototype-based inheritance under the hood. The major differences are:
Example (ES6 Class):
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound.`);
}
}
const dog = new Animal('Dog');
dog.speak(); // "Dog makes a sound."
Example (Prototype-based):
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a sound.`);
};
const dog = new Animal('Dog');
dog.speak(); // "Dog makes a sound."
Example (Inheritance with ES6):
class Dog extends Animal {
speak() {
console.log(`${this.name} barks.`);
}
}
const dog = new Dog('Rex');
dog.speak(); // "Rex barks."
A memory leak in JavaScript occurs when the application consumes more and more memory over time without releasing it. This can degrade performance and cause the application to crash. Some strategies to handle memory leaks:
Clear timeouts and intervals: Always clear setTimeout() and setInterval() when they are no longer needed.
const timerId = setInterval(() => {
// Some code
}, 1000);
clearInterval(timerId);
Event listeners: Always remove event listeners when no longer needed, especially for DOM elements that might get removed.
element.removeEventListener('click', handleClick);
The async/await syntax is built on Promises and allows asynchronous code to be written in a synchronous-looking style. Here's how it works:
Under the hood:
Example:
async function fetchData() {
const response = await fetch('https://api.example.com');
const data = await response.json();
return data;
}
fetchData().then(data => console.log(data));
Under the hood, this is equivalent to:
function fetchData() {
return fetch('https://api.example.com')
.then(response => response.json())
.then(data => data);
}
To implement a custom event system, you can use a pub/sub (publish/subscribe) pattern, where event listeners subscribe to certain events, and the event emitter triggers those events when needed.
Example:
class EventEmitter {
constructor() {
this.events = {};
}
on(event, listener) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
}
emit(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(listener => listener(...args));
}
}
off(event, listener) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(l => l !== listener);
}
}
}
// Usage:
const emitter = new EventEmitter();
function onUserLogin(user) {
console.log(`${user} logged in`);
}
emitter.on('userLogin', onUserLogin);
emitter.emit('userLogin', 'Alice'); // Logs: Alice logged in
emitter.off('userLogin', onUserLogin);
This basic EventEmitter class allows for adding listeners (on), emitting events (emit), and removing listeners (off).
Example (Shallow copy):
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { ...obj1 }; // Shallow copy
obj2.b.c = 3;
console.log(obj1.b.c); // 3 (obj1's nested object is affected)
Example (Deep copy using JSON.parse and JSON.stringify):
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = JSON.parse(JSON.stringify(obj1)); // Deep copy
obj2.b.c = 3;
console.log(obj1.b.c); // 2 (obj1 remains unaffected)
Example:
console.log('Start');
setTimeout(() => console.log('Timeout'), 0);
Promise.resolve().then(() => console.log('Promise'));
console.log('End');
// Output:
// Start
// End
// Promise
// Timeout
JavaScript uses automatic garbage collection to manage memory. It identifies objects that are no longer needed (unreachable) and frees up memory.
Garbage collection helps to prevent memory leaks and ensures the application does not consume excessive memory over time.
ES6 Modules (also called ECMAScript modules) provide a standardized way to organize and export JavaScript code. They use the import and export syntax to share code between files.
Example of ES6 Module:
math.js (module file):
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
app.js (importing the module):
import { add, subtract } from './math.js';
console.log(add(2, 3)); // 5
Key Features of ES6 Modules:
CommonJS modules, on the other hand, are primarily used in Node.js applications. They use require() to import modules and module.exports to export them.
Example of CommonJS Module:
math.js:
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
module.exports = { add, subtract };
app.js:
const { add, subtract } = require('./math');
console.log(add(2, 3)); // 5
Key Features of CommonJS:
An IIFE (Immediately Invoked Function Expression) is a function that is defined and executed immediately after its creation. It is commonly used to create a new scope, avoid polluting the global namespace, and isolate variables.
Syntax:
(function() {
// code here
})(); // IIFE
Alternatively, with an arrow function:
(() => {
// code here
})();
Use Cases:
Example:
(function() {
const privateVar = 'I am private';
console.log(privateVar); // 'I am private'
})();
console.log(privateVar); // Error: privateVar is not defined
A closure is a function that has access to its own scope, the scope in which it was created, and the global scope. Closures are powerful and are often used for:
function counter() {
let count = 0;
return {
increment: () => count++,
getCount: () => count
};
}
const counterInstance = counter();
console.log(counterInstance.getCount()); // 0
counterInstance.increment();
console.log(counterInstance.getCount()); // 1
function multiplier(factor) {
return function(number) {
return number * factor;
};
}
const doubler = multiplier(2);
console.log(doubler(5)); // 10
Both Object.create() and the new keyword are used to create new objects, but they differ in how they establish the object's prototype chain and constructor behavior.
Example:
const proto = { greet() { console.log('Hello!'); } };
const obj = Object.create(proto);
obj.greet(); // "Hello!"
Example:
function Person(name) {
this.name = name;
}
const person = new Person('Alice');
console.log(person.name); // 'Alice'
Key Differences:
Deep cloning involves creating a new object with an identical structure and values, including nested objects, without maintaining references to the original object's properties.
Methods to Deep Clone:
Example:
const original = { a: 1, b: { c: 2 } };
const clone = JSON.parse(JSON.stringify(original));
clone.b.c = 3;
console.log(original.b.c); // 2
Example (Recursive Clone):
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) return obj.map(item => deepClone(item));
const clonedObj = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = deepClone(obj[key]);
}
}
return clonedObj;
}
To improve the security of JavaScript applications, several strategies should be employed:
In JavaScript, concurrency is achieved via the event loop, where tasks are handled asynchronously, but there's only a single thread of execution. However, parallelism can be achieved using Web Workers in the browser or using multi-threading in environments like Node.js.
A service worker is a special JavaScript file that runs in the background and enables features like offline access, background sync, and push notifications. It acts as a proxy between the web application and the network, allowing you to intercept network requests and cache responses.
Key Features:
Service workers are essential for building Progressive Web Apps (PWAs).
Example:
// In service-worker.js
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('my-cache').then((cache) => {
return cache.addAll(['/index.html', '/style.css']);
})
);
});
Debounce:
function debounce(func, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), delay);
};
}
Throttle:
function throttle(func, limit) {
let lastCall = 0;
return function(...args) {
const now = new Date().getTime();
if (now - lastCall >= limit) {
func(...args);
lastCall = now;
}
};
}
The Singleton Pattern ensures that a class has only one instance and provides a global point of access to that instance.
Example:
const Singleton = (function() {
let instance;
function createInstance() {
return { message: "I am the only instance" };
}
return {
getInstance: function() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true
Use Cases:
Generators are special functions in JavaScript that can be paused and resumed, allowing them to maintain their state between function calls. A generator function is defined using the function* syntax, and it yields values one at a time using the yield keyword.
Example of a Generator:
function* simpleGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = simpleGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
Using Generators for Asynchronous Tasks:
Generators can be useful for complex async workflows when combined with a control flow mechanism, such as co (a library) or custom handling, to yield promises and resume once the promises are resolved.
Example: Using Generators with Async Tasks
function* fetchData() {
const user = yield fetch('https://api.example.com/user');
const posts = yield fetch(`https://api.example.com/posts/${user.id}`);
return posts;
}
function runGenerator(gen) {
const iterator = gen();
function handleResult(result) {
if (result.done) return Promise.resolve(result.value);
return Promise.resolve(result.value).then(res => handleResult(iterator.next(res)));
}
return handleResult(iterator.next());
}
runGenerator(fetchData)
.then(data => console.log('Fetched data:', data))
.catch(err => console.error(err));
Generators provide a way to simplify asynchronous code flow while avoiding "callback hell." However, async/await is generally preferred nowadays for simpler syntax.
The bind() method creates a new function that, when called, has its this value set to a specific object, and any provided arguments are prepended to the arguments provided to the new function when called.
Key Points:
Example:
const obj = { name: 'Alice' };
function greet() {
console.log(`Hello, ${this.name}!`);
}
const greetAlice = greet.bind(obj);
greetAlice(); // Output: "Hello, Alice!"
In this case, bind() ensures that the greet() function always uses obj as its execution context, even if it's called in a different context.