JavaScript is a fundamental skill for any aspiring web developer, and landing a job in this field can be a challenging task, especially for freshers. One of the most crucial steps in the interview process is the technical interview, where your JavaScript skills are put to the test. To help you prepare and boost your confidence, we've compiled a list of the top 50 basic JavaScript interview questions and answers that are commonly asked in interviews.
Hoisting describes the behavior of variable declarations in JavaScript. Declarations using var
are "moved" to the top of their scope during compilation. Only the declaration is hoisted, not the initialization.
Example with var
:
console.log(foo); // undefinedvar foo = 1;console.log(foo); // 1
Visualized as:
var foo;console.log(foo); // undefinedfoo = 1;console.log(foo); // 1
Variables with let
, const
, and class
:
These are also hoisted but not initialized. Accessing them before declaration results in a ReferenceError
.
console.log(y); // ReferenceError: Cannot access 'y' before initializationlet y = 'local';console.log(z); // ReferenceError: Cannot access 'z' before initializationconst z = 'local';console.log(Foo); // ReferenceError: Cannot access 'Foo' before initializationclass Foo {constructor() {}}
Function Expressions:
Only the declaration is hoisted.
console.log(bar); // undefinedbar(); // Uncaught TypeError: bar is not a functionvar bar = function () {console.log('BARRRR');};
Function Declarations:
Both declaration and definition are hoisted.
console.log(foo); // [Function: foo]foo(); // 'FOOOOO'function foo() {console.log('FOOOOO');}
Import Statements:
Imports are hoisted, making them available throughout the module, with side effects occurring before other code runs.
foo.doSomething(); // Works normally.import foo from './modules/foo';
let
, var
or const
?var
: Function-scoped or globally scoped.let
and const
: Block-scoped (only accessible within the nearest set of curly braces).Example:
function foo() {var bar = 1;let baz = 2;const qux = 3;console.log(bar); // 1console.log(baz); // 2console.log(qux); // 3}console.log(bar); // ReferenceErrorconsole.log(baz); // ReferenceErrorconsole.log(qux); // ReferenceError
if (true) {var bar = 1;let baz = 2;const qux = 3;}console.log(bar); // 1console.log(baz); // ReferenceErrorconsole.log(qux); // ReferenceError
var
and let
: Can be declared without an initial value.const
: Must be initialized at the time of declaration.Example:
var foo; // Oklet bar; // Okconst baz; // SyntaxError
var
: Allows redeclaration.let
and const
: Do not allow redeclaration.Example:
var foo = 1;var foo = 2; // Oklet baz = 3;let baz = 4; // SyntaxError
var
and let
: Allow reassignment.const
: Does not allow reassignment.Example:
var foo = 1;foo = 2; // Oklet bar = 3;bar = 4; // Okconst baz = 5;baz = 6; // TypeError
var
: Variables are hoisted and initialized to undefined
.let
and const
: Variables are hoisted but not initialized, causing a ReferenceError
if accessed before declaration.Example:
console.log(foo); // undefinedvar foo = 'foo';console.log(baz); // ReferenceErrorlet baz = 'baz';console.log(bar); // ReferenceErrorconst bar = 'bar';
==
and ===
in JavaScript?==
)Examples:
42 == '42'; // true0 == false; // truenull == undefined; // true[] == false; // true'' == false; // true
###Strict Equality Operator (===
)
true
.Examples:
42 === '42'; // false0 === false; // falsenull === undefined; // false[] === false; // false'' === false; // false
Use ==
only when comparing against null
or undefined
for convenience.
var a = null;console.log(a == null); // trueconsole.log(a == undefined); // true
Prefer ===
for all other comparisons to avoid pitfalls of type coercion and ensure both value and type are the same.
The event loop is crucial for handling asynchronous operations in JavaScript, allowing single-threaded execution without blocking.
1. Call Stack:
2. Web APIs/Node.js APIs:
setTimeout()
, HTTP requests) on separate threads.3. Task Queue (Macrotask Queue):
setTimeout()
, setInterval()
, and UI events.4. Microtask Queue:
Promise
callbacks).console.log('Start');setTimeout(() => {console.log('Timeout 1');}, 0);Promise.resolve().then(() => {console.log('Promise 1');});setTimeout(() => {console.log('Timeout 2');}, 0);console.log('End');
Console Output:
StartEndPromise 1Timeout 1Timeout 2
Explanation:
Start
and End
are logged first (synchronous).Promise 1
is logged next (microtask).Timeout 1
and Timeout 2
are logged last (macrotasks).Understanding the event loop helps write efficient, non-blocking JavaScript code.
Event delegation is an efficient way to handle events on multiple child elements by attaching a single event listener to a common parent element. This is useful for managing events on many similar elements, like list items.
event.target
to identify the actual element that triggered the event.// HTML:// <ul id="item-list">// <li>Item 1</li>// <li>Item 2</li>// <li>Item 3</li>// </ul>const itemList = document.getElementById('item-list');itemList.addEventListener('click', (event) => {if (event.target.tagName === 'LI') {console.log(`Clicked on ${event.target.textContent}`);}});
A single click listener on <ul>
handles clicks on any <li>
due to event bubbling.
Dynamic Content:
const buttonContainer = document.getElementById('button-container');const addButton = document.getElementById('add-button');buttonContainer.addEventListener('click', (event) => {if (event.target.tagName === 'BUTTON') {console.log(`Clicked on ${event.target.textContent}`);}});addButton.addEventListener('click', () => {const newButton = document.createElement('button');newButton.textContent = `Button ${buttonContainer.children.length + 1}`;buttonContainer.appendChild(newButton);});
Simplifying Code:
const userForm = document.getElementById('user-form');userForm.addEventListener('input', (event) => {const { name, value } = event.target;console.log(`Changed ${name}: ${value}`);});
this
works in JavaScriptThe this
keyword in JavaScript can be quite confusing as its value depends on how a function is called. Here are the main rules that determine the value of this
:
new
KeywordCreates a new object and sets this
to that object.
function Person(name) {this.name = name;}const person = new Person('Alice');console.log(person.name); // 'Alice
apply
, call
, or bind
Explicitly sets this
to a specified object.
javascriptCopy codefunction greet() {console.log(this.name);}const person = { name: 'Alice' };greet.call(person); // 'Alice'
this
is bound to the object the method is called on.
const obj = {name: 'Alice',greet: function () {console.log(this.name);},};obj.greet(); // 'Alice'
In non-strict mode, defaults to the global object (window
in browsers); in strict mode, defaults to undefined
.
function greet() {console.log(this); // global object or undefined}greet();
Inherit this
from their lexical enclosing scope.
const obj = {name: 'Alice',greet: () => {console.log(this.name); // `this` refers to the enclosing scope},};obj.greet(); // undefined
this
ES2015 introduced arrow functions which capture this
from their lexical scope. This can simplify code but requires caution when integrating with libraries expecting traditional function context.
Example:
function Timer() {this.seconds = 0;setInterval(() => {this.seconds++; // `this` refers to the Timer instanceconsole.log(this.seconds);}, 1000);}const timer = new Timer();
sessionStorage
and localStorage
.Cookies, localStorage, and sessionStorage are key client-side storage mechanisms in web applications, each serving distinct purposes:
Purpose: Stores small data pieces sent to the server with HTTP requests.
Capacity: Limited to around 4KB per domain.
Lifespan: Can have expiration dates; session cookies are cleared when the browser closes.
Access: Domain-specific; accessible across pages and subdomains.
Security: Supports HttpOnly and Secure flags to restrict JavaScript access and ensure HTTPS transmission.
Example Usage:
// Set a cookie with an expirydocument.cookie ='auth_token=abc123def; expires=Fri, 31 Dec 2024 23:59:59 GMT; path=/';// Read all cookies (no direct method for specific cookies)console.log(document.cookie);// Delete a cookiedocument.cookie ='auth_token=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
localStorage
Purpose: Stores data persistently on the client-side.
Capacity: Around 5MB per origin.
Lifespan: Data remains until explicitly cleared.
Access: Available across all tabs and windows within the same origin.
Security: All JavaScript on the page can access localStorage values.
Example Usage:
// Set an item in localStoragelocalStorage.setItem('key', 'value');// Get an item from localStorageconsole.log(localStorage.getItem('key'));// Remove an item from localStoragelocalStorage.removeItem('key');// Clear all data in localStoragelocalStorage.clear();
sessionStorage
Purpose: Stores session-specific data that persists until the browser or tab is closed.
Capacity: Similar to localStorage, around 5MB per origin.
Lifespan: Cleared when the tab or browser closes; reloading the page retains data.
Access: Limited to the current tab or window.
Security: All JavaScript on the page can access sessionStorage values.
Example Usage:
// Set an item in sessionStoragesessionStorage.setItem('key', 'value');// Get an item from sessionStorageconsole.log(sessionStorage.getItem('key'));// Remove an item from sessionStoragesessionStorage.removeItem('key');// Clear all data in sessionStoragesessionStorage.clear();
<script>
, <script async>
and <script defer>
<script>
TagThe <script>
tag is used to include JavaScript in a web page. When used without async
or defer
attributes:
Example:
<!doctype html><html><head><title>Regular Script</title></head><body><h1>Regular Script Example</h1><p>This content appears before the script executes.</p><script src="regular.js"></script><p>This content appears after the script executes.</p></body></html>
<script async>
TagExample:
<!doctype html><html><head><title>Async Script</title></head><body><h1>Async Script Example</h1><p>This content appears before the async script executes.</p><script async src="async.js"></script><p>This content may appear before or after the async script executes.</p></body></html>
<script defer>
Tag:
DOMContentLoaded
.Example:
<!doctype html><html><head><title>Deferred Script</title></head><body><h1>Deferred Script Example</h1><p>This content appears before the deferred script executes.</p><script defer src="deferred.js"></script><p>This content appears before the deferred script executes.</p></body></html>
null
, undefined
or undeclared?Undeclared: A variable that is not declared using var
, let
, or const
will be created globally and can cause errors. Avoid them by using try/catch
blocks to detect them.
undefined
: A declared variable without an assigned value is undefined
. Use ===
or typeof
to check for undefined
. Note that ==
will also return true
for null
.
null
: A variable explicitly assigned null
represents no value. Use ===
to check for null
. Don't use ==
as it will also return true
for undefined
.
Best Practices:
null
to variables if you don't intend to use them yet..call
and .apply
in JavaScript?.call
and .appl
are used to invoke functions, setting this
within the function. The difference lies in how they handle arguments:
Memory Aid:
Example:
javascriptCopy codefunction add(a, b) {return a + b;}console.log(add.call(null, 1, 2)); // 3console.log(add.apply(null, [1, 2])); // 3// ES6 with spread operatorconsole.log(add.call(null, ...[1, 2])); // 3
Function.prototype.bind
Function.prototype.bind
creates a new function with a specific this
context and optionally preset arguments. It's useful for maintaining the correct this
value in methods passed to other functions.
Example:
const john = {age: 42,getAge: function () {return this.age;},};console.log(john.getAge()); // 42const unboundGetAge = john.getAge;console.log(unboundGetAge()); // undefinedconst boundGetAge = john.getAge.bind(john);console.log(boundGetAge()); // 42const mary = { age: 21 };const boundGetAgeMary = john.getAge.bind(mary);console.log(boundGetAgeMary()); // 21
Its main purposes are:
this
to preserve context: The primary function of bind
is to attach the this
value of a function to a specific object. When you use func.bind(thisArg)
, it generates a new function with the same code as func
, but with this
permanently set to thisArg
.bind
also enables you to pre-set arguments for the new function. Any arguments provided to bind
after thisArg
will be prepended to the argument list when the new function is invoked.bind
allows you to borrow methods from one object and use them on another object, even if the methods were not initially designed for that object.The advantage of using the arrow syntax for a method in a constructor is that it automatically binds the this
value to the constructor's this
context. This means that when the method is called, it will always refer to the constructor's this
context, rather than the global scope or some other unexpected context.
In traditional function expressions, the this
value is determined by how the function is called, which can lead to unexpected behavior if not properly bound. By using an arrow function, you can ensure that the this
value is always bound to the constructor's this
context, making your code more predictable and easier to maintain.
For example, in the code snippet:
const Person = function (name) {this.name = name;this.sayName1 = function () {console.log(this.name);};this.sayName2 = () => {console.log(this.name);};};const john = new Person('John');const dave = new Person('Dave');john.sayName1(); // Johnjohn.sayName2(); // John// `this` can change for regular functions but not for arrow functionsjohn.sayName1.call(dave); // Davejohn.sayName2.call(dave); // John
The sayName1
method uses a traditional function expression, which means its this
value is determined by how it's called. If you call john.sayName1.call(dave)
, the this
value will be dave
, and the method will log Dave
to the console.
On the other hand, the sayName2
method uses an arrow function, which means its this
value is automatically bound to the constructor's this
context. If you call john.sayName2.call(dave)
, the this
value will still be john
, and the method will log John
to the console.
This can be particularly helpful in React class components, where you often need to pass methods as props to child components. By using arrow functions, you can ensure that the methods always refer to the correct this
context, without having to manually bind this
in the constructor.
Prototypical inheritance allows objects to inherit properties and methods from other objects using a prototype-based model.
Object.getPrototypeOf()
and Object.setPrototypeOf()
.function Person(name, age) {this.name = name;this.age = age;}Person.prototype.sayHello = function () {console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`,);};let john = new Person('John', 30);john.sayHello(); // "Hello, my name is John and I am 30 years old."
JavaScript looks for properties/methods on the object, then its prototype, and so on up the chain until null
.
Functions used with new
to create objects, setting their prototype to the constructor's prototype
.
function Animal(name) {this.name = name;}Animal.prototype.sayName = function () {console.log(`My name is ${this.name}`);};function Dog(name, breed) {Animal.call(this, name);this.breed = breed;}Dog.prototype = Object.create(Animal.prototype);Dog.prototype.bark = function () {console.log('Woof!');};let fido = new Dog('Fido', 'Labrador');fido.bark(); // "Woof!"fido.sayName(); // "My name is Fido"
Object.create()
Creates a new object with a specified prototype.
let proto = {greet: function () {console.log(`Hello, my name is ${this.name}`);},};let person = Object.create(proto);person.name = 'John';person.greet(); // "Hello, my name is John"
function Person(){}
, const person = Person()
, and const person = new Person()
?function Person(){}
is a standard function declaration in JavaScript. When written in PascalCase, it follows the convention for functions intended to be used as constructors.
const person = Person()
simply calls the function and executes its code. If no return value is specified, person
will be undefined
. This is not a constructor call and does not create a new object.
const person = new Person()
creates a new object using the Person
constructor function. The new
keyword creates a new object and sets its prototype to Person.prototype
. The this
keyword inside the constructor function refers to the new object being created.
foo
between function foo() {}
and var foo = function() {}
Syntax: function foo() {}
Description: Defines a named function that can be called throughout the enclosing scope.
Example:
function foo() {console.log('FOOOOO');}
Syntax: var foo = function() {}
Description: Defines a function and assigns it to a variable, often used in specific contexts.
Example:
var foo = function () {console.log('FOOOOO');};
Hoisting:
Function Declarations: The entire function is hoisted; can be called before its definition.
foo(); // 'FOOOOO'function foo() {console.log('FOOOOO');}
Function Expressions: Only the variable is hoisted, not the function body; calling it before definition results in an error.
foo(); // Uncaught TypeError: foo is not a functionvar foo = function () {console.log('FOOOOO');};
Name Scope:
Function Expressions: These can be named internally, but the name is only accessible within the function.
const myFunc = function namedFunc() {console.log(namedFunc); // Works};console.log(namedFunc); // undefined
Function Declarations:
Function Expressions:
Here are the various ways to create objects in JavaScript:
Object Literals ({}
): Simplest way to create objects using key-value pairs within curly braces.
const person = {firstName: 'John',lastName: 'Doe',};
Object() Constructor: Using the new
keyword with the built-in Object
constructor to create an object.
const person = new Object();person.firstName = 'John';person.lastName = 'Doe';
Object.create() Method: Creating a new object using an existing object as a prototype.
const personPrototype = {greet() {console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`,);},};const person = Object.create(personPrototype);person.name = 'John';person.age = 30;person.greet(); // Output: Hello, my name is John and I'm 30 years old.
ES2015 Classes: Defining a blueprint for objects using classes, similar to other programming languages.
class Person {constructor(name, age) {this.name = name;this.age = age;}greet = function () {console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`,);};}const person = new Person('John', 30);person.greet(); // Output: Hello, my name is John and I'm 30 years old.
Constructor Functions: Reusable blueprints for objects, using the new
keyword to create instances.
// Constructor functionfunction Person(name, age) {this.name = name;this.age = age;this.greet = function () {console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`,);};}const person = new Person('John', 30);person.greet(); // Output: Hello, my name is John and I'm 30 years old.
Note: Constructor functions are less commonly used now that ES2015 classes are widely supported.
A higher-order function is a function that:
Takes another function as an argument: A function that accepts another function as a parameter.
function greet(name) {return `Hello, ${name}!`;}function greetName(greeter, name) {console.log(greeter(name));}greetName(greet, 'Alice'); // Output: Hello, Alice!
Returns a function as its result: A function that returns another function as its output.
function multiplier(factor) {return function (num) {return num * factor;};}const double = multiplier(2);console.log(double(5)); // Output: 10
In other words, a higher-order function is a function that operates on other functions, either by taking them as input or by producing them as output.
Uses function constructors and prototypes.
Example:
function Person(name, age) {this.name = name;this.age = age;}Person.prototype.greet = function () {console.log('Hello, my name is ' +this.name +' and I am ' +this.age +' years old.',);};var person1 = new Person('John', 30);person1.greet(); // Hello, my name is John and I am 30 years old.
Uses the class
syntax, making code more readable and adding features.
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.`,);}}const person1 = new Person('John', 30);person1.greet(); // Hello, my name is John and I am 30 years old.
class
keyword, more concise and easier to understandstatic
keywordObject.create()
and manual prototype chain settingextends
keyword, simpler and more intuitivesuper
keyword to call parent class's constructor and methodsEvent bubbling is a mechanism in the DOM (Document Object Model) where an event, such as a click, is first triggered on the target element and then propagates upward through the DOM tree to the root of the document.
Bubbling Phase:
Description: During the bubbling phase, the event starts at the target element and bubbles up through its ancestors in the DOM hierarchy. Event handlers attached to the target element and its ancestors can all potentially receive and respond to the event.
Example:
// HTML:// <div id="parent">// <button id="child">Click me!</button>// </div>const parent = document.getElementById('parent');const child = document.getElementById('child');parent.addEventListener('click', () => {console.log('Parent element clicked');});child.addEventListener('click', () => {console.log('Child element clicked');});
When you click the Click me!
button, both the child and parent event handlers will be triggered due to event bubbling.
Stopping Event Bubbling:
Method: Use stopPropagation()
to stop the event from bubbling up the DOM tree.
Example:
child.addEventListener('click', (event) => {console.log('Child element clicked');event.stopPropagation();});
Event capturing is a propagation mechanism in the DOM where an event, such as a click, is first triggered at the root of the document and then flows down through the DOM tree to the target element.
Event Propagation Phases:
Enabling Event Capturing:
{ capture: true }
as the third argument to addEventListener()
.Example:
// HTML:// <div id="parent">// <button id="child">Click me!</button>// </div>const parent = document.getElementById('parent');const child = document.getElementById('child');parent.addEventListener('click',() => {console.log('Parent element clicked (capturing)');},true, // Enable capturing phase);child.addEventListener('click', () => {console.log('Child element clicked');});
When you click the Click me!
button, the parent element's capturing handler will be triggered before the child element's handler.
Stopping Propagation:
Use stopPropagation()
to prevent the event from traveling further down the DOM tree during the capturing phase.
Example:
parent.addEventListener('click',(event) => {console.log('Parent element clicked (capturing)');event.stopPropagation(); // Stop event propagation},true,);child.addEventListener('click', () => {console.log('Child element clicked');});
In this example, only the parent event listener will be called when you click the "Click me!" button, as the event propagation is stopped at the parent element.
mouseenter
and mouseover
event in JavaScript and browsers?mouseenter
mouseover
Example:
const fs = require('fs');const data = fs.readFileSync('large-file.txt', 'utf8');console.log(data); // Blocks until file is readconsole.log('End of the program');
Example:
console.log('Start of the program');fetch('https://api.example.com/data').then((response) => response.json()).then((data) => console.log(data)) // Non-blocking.catch((error) => console.error(error));console.log('End of program');
AJAX is a set of web development techniques using various web technologies on the client side to create asynchronous web applications. Unlike traditional web applications where each user interaction triggers a full page reload, AJAX allows web applications to send data to and retrieve data from a server asynchronously without interfering with the display and behavior of the existing page. This enables dynamic updates to the web page without the need to reload it.
Key Points:
XMLHttpRequest
, but fetch()
is now preferred for modern web applications.XMLHttpRequest
APIExample:
let xhr = new XMLHttpRequest();xhr.onreadystatechange = function () {if (xhr.readyState === XMLHttpRequest.DONE) {if (xhr.status === 200) {console.log(xhr.responseText);} else {console.error('Request failed: ' + xhr.status);}}};xhr.open('GET', 'https://jsonplaceholder.typicode.com/todos/1', true);xhr.send();
XMLHttpRequest
, sets up a callback function to handle state changes, opens a request to a URL, and sends the request.fetch()
APIExample:
fetch('https://jsonplaceholder.typicode.com/todos/1').then((response) => {if (!response.ok) {throw new Error('Network response was not ok');}return response.json();}).then((data) => console.log(data)).catch((error) => console.error('Fetch error:', error));
.then()
to parse JSON data, and manages errors with .catch()
.fetch
fetch()
initiates an asynchronous request to fetch a resource from a URL.
Example:
fetch('https://api.example.com/data', {method: 'GET', // or 'POST', 'PUT', 'DELETE', etc.headers: {'Content-Type': 'application/json',},});
fetch()
returns a Promise that resolves to a Response
object representing the server's response.The Response
object offers methods to handle the body content, such as .json()
, .text()
, .blob()
.
Example:
fetch('https://api.example.com/data').then((response) => response.json()).then((data) => console.log(data)).catch((error) => console.error('Error:', error));
fetch()
is asynchronous, allowing the browser to continue executing other tasks while waiting for the server response..then()
, .catch()
) are handled in the microtask queue as part of the event loop.fetch()
configures various request aspects, such as HTTP method, headers, body, credentials, and caching..catch()
or try/catch
with async/await
.AJAX (Asynchronous JavaScript and XML) enables web pages to send and retrieve data asynchronously, allowing for dynamic updates without full page reloads.
XMLHttpRequest
and fetch()
?Both XMLHttpRequest (XHR)
and fetch()
enable asynchronous HTTP requests in JavaScript, but differ in syntax, handling, and features.
setRequestHeader
method.send
method.body
property in the options parameter.responseType
to handle different formats..then
for accessing data.onerror
event..catch
method.abort()
method.AbortController
for request cancellation.onprogress
event.Choosing Between Them: fetch()
is generally preferred due to its cleaner syntax and promise-based handling, but XMLHttpRequest
may still be useful for specific cases like progress tracking.
JavaScript has various data types categorized into two groups: primitive and non-primitive (reference) types.
true
or false
.Determining Data Types: JavaScript is dynamically typed, meaning variables can hold different data types over time. Use the typeof
operator to determine a variable's type.
Iterating over object properties and arrays is very common in JavaScript and we have various ways to achieve this. Here are some of the ways to do it:
for...in
StatementLoops over all enumerable properties of an object, including inherited ones.
for (const property in obj) {if (Object.hasOwn(obj, property)) {console.log(property);}}
Object.keys()
Returns an array of an object's own enumerable property names.
Object.keys(obj).forEach((property) => console.log(property));
Object.entries()
Returns an array of a given object's own enumerable string-keyed property [key, value] pairs.
Object.entries(obj).forEach(([key, value]) =>console.log(`${key}: ${value}`),);
Object.getOwnPropertyNames()
Returns an array of all properties (including non-enumerable ones) found directly upon a given object.
Object.getOwnPropertyNames(obj).forEach((property) =>console.log(property),);
for
LoopTraditional loop over array elements.
for (let i = 0; i < arr.length; i++) {console.log(arr[i]);}
Array.prototype.forEach()
Executes a provided function once for each array element.
arr.forEach((element, index) => console.log(element, index));
for...of
StatementLoops over iterable objects like arrays.
for (let element of arr) {console.log(element);}
Array.prototype.entries()
Provides both the index and value of each array element in a for...of
loop.
for (let [index, elem] of arr.entries()) {console.log(index, ': ', elem);}
Introduced in ES2015, the spread syntax (...
) is useful for copying and merging arrays and objects without modifying the originals. It's commonly used in functional programming, Redux, and RxJS.
Copying Arrays/Objects: Creates shallow copies.
const array = [1, 2, 3];const newArray = [...array]; // [1, 2, 3]const obj = { name: 'John', age: 30 };const newObj = { ...obj, city: 'New York' }; // { name: 'John', age: 30, city: 'New York' }
Merging Arrays/Objects: Merges them into a new one.
const arr1 = [1, 2, 3];const arr2 = [4, 5, 6];const mergedArray = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]const obj1 = { foo: 'bar' };const obj2 = { qux: 'baz' };const mergedObj = { ...obj1, ...obj2 }; // { foo: 'bar', qux: 'baz' }
Function Arguments: Passes array elements as individual arguments.
const numbers = [1, 2, 3];Math.max(...numbers); // Same as Math.max(1, 2, 3)
Array vs. Object Spreads: Only iterables can be spread into arrays; arrays can be spread into objects.
const array = [1, 2, 3];const obj = { ...array }; // { 0: 1, 1: 2, 2: 3 }
The rest syntax (...
) gathers multiple elements into an array or object, the inverse of the spread syntax.
Function Parameters: Collects remaining arguments into an array.
function addFiveToNumbers(...numbers) {return numbers.map((x) => x + 5);}const result = addFiveToNumbers(4, 5, 6, 7); // [9, 10, 11, 12]
Array Destructuring: Collects remaining elements into a new array.
const [first, second, ...remaining] = [1, 2, 3, 4, 5];// first: 1, second: 2, remaining: [3, 4, 5]
Object Destructuring: Collects remaining properties into a new object.
const { e, f, ...others } = { e: 1, f: 2, g: 3, h: 4 };// e: 1, f: 2, others: { g: 3, h: 4 }
Rest Parameter Rules: Must be the last parameter.
function addFiveToNumbers(arg1, ...numbers, arg2) {// Error: Rest element must be last element.}
Map
object and a plain object in JavaScript?size
property to get the number of key-value pairs.forEach
, keys()
, values()
, and entries()
.Object.keys()
, Object.values()
, or Object.entries()
for iteration.// Mapconst map = new Map();map.set('key1', 'value1');map.set({ key: 'key2' }, 'value2');console.log(map.size); // 2// Plain Objectconst obj = { key1: 'value1' };obj[{ key: 'key2' }] = 'value2';console.log(Object.keys(obj).length); // 1 (keys are strings)
Map
/Set
vs WeakMap
/WeakSet
?The main distinctions between Map
/Set
and WeakMap
/WeakSet
in JavaScript are as follows:
Map
and Set
accept keys of any type (objects, primitive values), whereas WeakMap
and WeakSet
exclusively use objects as keys, excluding primitive values like strings or numbers.Map
and Set
retain strong references to their keys and values, preventing their disposal by garbage collection. In contrast, WeakMap
and WeakSet
employ weak references for keys (objects), allowing these objects to be collected by garbage collection if no other strong references persist.Map
and Set
are enumerable and can be iterated over, while those in WeakMap
and WeakSet
are non-enumerable, precluding retrieval of key or value lists directly from them.Map
and Set
possess a size
property that indicates the number of elements they contain. In contrast, WeakMap
and WeakSet
lack a size
property because their size may vary as a result of garbage collection.Map
and Set
serve well as general-purpose data structures and for caching purposes. Conversely, WeakMap
and WeakSet
are primarily suited for storing metadata or additional object-related data without impeding the object's potential garbage collection when no longer needed.One practical use case for the arrow function syntax in JavaScript is simplifying callback functions, particularly in scenarios where you need concise, inline function definitions. Here's an example:
Use Case: Mapping an Array
Suppose you have an array of numbers and you want to double each number using the map
function.
// Traditional function syntaxconst numbers = [1, 2, 3, 4, 5];const doubledNumbers = numbers.map(function (number) {return number * 2;});console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]
Using arrow function syntax, you can achieve the same result more succinctly:
// Arrow function syntaxconst numbers = [1, 2, 3, 4, 5];const doubledNumbers = numbers.map((number) => number * 2);console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]
In asynchronous programming, a callback function is passed as an argument to another function and invoked when a task completes, such as fetching data or handling I/O operations. Here's a concise explanation:
function fetchData(callback) {setTimeout(() => {const data = { name: 'John', age: 30 };callback(data);}, 1000);}fetchData((data) => {console.log(data); // { name: 'John', age: 30 }});
Debouncing delays function execution until a specified time has passed since its last call, useful for tasks like search input handling.
function debounce(func, delay) {let timeoutId;return (...args) => {clearTimeout(timeoutId);timeoutId = setTimeout(() => func.apply(this, args), delay);};}
Throttling limits function execution to at most once within a specified interval, beneficial for tasks like handling frequent events such as window resizing or scrolling.
function throttle(func, limit) {let inThrottle;return (...args) => {if (!inThrottle) {func.apply(this, args);inThrottle = true;setTimeout(() => (inThrottle = false), limit);}};}
These techniques optimize performance and manage event-driven behaviors effectively in JavaScript applications.
Destructuring assignment simplifies extracting values from arrays or properties from objects into separate variables:
// Array destructuringconst [a, b] = [1, 2];// Object destructurinconst { name, age } = { name: 'John', age: 30 };
This syntax uses square brackets for arrays and curly braces for objects, enabling concise variable assignment directly from data structures.
Hoisting moves function declarations to the top of their scope during compilation, allowing them to be called before their actual placement in the code. Function expressions and arrow functions, however, must be defined before they are called to avoid errors.
// Function declarationhoistedFunction(); // Works finefunction hoistedFunction() {console.log('This function is hoisted');}// Function expressionnonHoistedFunction(); // Throws an errorvar nonHoistedFunction = function () {console.log('This function is not hoisted');};
In ES2015, classes use extends
to enable one class to inherit properties and methods from another. The super
keyword accesses the parent class's constructor and methods.
class Animal {constructor(name) {this.name = name;}speak() {console.log(`${this.name} makes a noise.`);}}class Dog extends Animal {constructor(name, breed) {super(name);this.breed = breed;}speak() {console.log(`${this.name} barks.`);}}const dog = new Dog('Rex', 'German Shepherd');dog.speak(); // Output: Rex barks.
Here, Dog
inherits from Animal
, showcasing how classes streamline inheritance and method overriding in JavaScript.
Lexical scoping in JavaScript determines variable access based on its position in the source code. Nested functions can access variables from their outer scope.
function outerFunction() {let outerVariable = 'I am outside!';function innerFunction() {console.log(outerVariable); // 'I am outside!'}innerFunction();}outerFunction();
Here, innerFunction
can access outerVariable
due to lexical scoping rules.
Scope in JavaScript determines the visibility of variables and functions within different parts of the code. There are three main types: global scope, function scope, and block scope.
// Global scopevar globalVar = 'I am global';function myFunction() {// Function scopevar functionVar = 'I am in a function';if (true) {// Block scopelet blockVar = 'I am in a block';console.log(blockVar); // Accessible here}// console.log(blockVar); // Throws an error}console.log(globalVar); // Accessible here// console.log(functionVar); // Throws an error
Global scope variables are accessible throughout the code, while function scope variables are limited to the function they are declared in. Block scope, introduced in ES6, confines variables to the block they are declared within (e.g., within curly braces ).
The spread operator (...) in JavaScript expands elements of an iterable (like arrays or objects) into individual elements. It's used for copying arrays or objects, merging them, and passing array elements as function arguments.
// Copying an arrayconst arr1 = [1, 2, 3];const arr2 = [...arr1];// Merging arraysconst arr3 = [4, 5, 6];const mergedArray = [...arr1, ...arr3];// Copying an objectconst obj1 = { a: 1, b: 2 };const obj2 = { ...obj1 };// Merging objectsconst obj3 = { c: 3, d: 4 };const mergedObject = { ...obj1, ...obj3 };// Passing array elements as function argumentsconst sum = (x, y, z) => x + y + z;const numbers = [1, 2, 3];console.log(sum(...numbers)); // Output: 6
The spread operator simplifies tasks like copying, merging, and function argument handling by expanding iterable elements into individual components.
this
binding in event handlersIn JavaScript, the this
keyword refers to the object executing the current code. In event handlers, this
usually points to the element that triggered the event. However, its value can vary based on how the handler is defined and invoked. To ensure this
refers correctly, methods like bind()
, arrow functions, or explicit context assignment are used.
These approaches help maintain the intended context for this
within event handling functions, ensuring predictable behavior across different event-triggering scenarios in JavaScript applications.
Classical Inheritance: In languages like Java and C++, classes inherit from other classes through a hierarchical structure. Instances are created from classes using constructors.
Prototypal Inheritance: In JavaScript, objects inherit directly from other objects. Objects serve as prototypes, and new objects are created based on existing ones.
Classical inheritance uses classes for instantiation, while prototypal inheritance leverages object linkage for property and behavior inheritance, highlighting JavaScript's unique approach to object-oriented programming.
document.querySelector()
and document.getElementById()
document.querySelector()
selects elements using CSS selectors and returns the first match.
const element = document.querySelector('.my-class');
document.getElementById()
selects an element by its ID attribute and returns the element with that specific ID.
const elementById = document.getElementById('my-id');
While document.querySelector()
offers flexibility with CSS selectors, document.getElementById()
is straightforward for selecting elements by their unique IDs in the DOM.
Dot Notation: Concise and straightforward, it accesses object properties using valid identifiers.
const obj = { name: 'Alice', age: 30 };console.log(obj.name); // Alice
Bracket Notation: Flexible, it accesses properties using strings, suitable for names with special characters or dynamic properties.
const obj = { name: 'Alice', 'favorite color': 'blue' };console.log(obj['favorite color']); // blue
Dot notation is clear for standard properties, while bracket notation handles special cases like dynamic or non-standard property names effectively.
Accessible from anywhere in the code.
var globalVar = "I'm global"; // Global scope
Limited to the function where it's declared.
function myFunction() {var functionVar = "I'm in a function"; // Function scope}
Restricted to the block where let
or const
is used.
function myFunction() {if (true) {let blockVar = "I'm in a block"; // Block scopeconsole.log(blockVar); // Accessible here}// console.log(blockVar); // ReferenceError: blockVar is not defined}
These scopes define where variables can be accessed, from global access throughout the code to specific function or block-level access for better control and encapsulation of variables.
Duplicates top-level properties; nested objects remain referenced.
let obj1 = { a: 1, b: { c: 2 } };let shallowCopy = Object.assign({}, obj1);shallowCopy.b.c = 3;console.log(obj1.b.c); // 3
Duplicates all levels, creating independent nested objects.
let deepCopy = JSON.parse(JSON.stringify(obj1));deepCopy.b.c = 4;console.log(obj1.b.c); // 2
Shallow copies share references to nested objects, while deep copies create entirely new instances, ensuring independent modifications.
var
, let
, and const
var
:
console.log(myVar); // undefinedvar myVar = 'Hello';
let
and const
:
console.log(myLet); // ReferenceError: Cannot access 'myLet' before initializationlet myLet = 'World';console.log(myConst); // ReferenceError: Cannot access 'myConst' before initializationconst myConst = '!';
const
:
const PI = 3.14;PI = 3.14159; // TypeError: Assignment to constant variable.
Closures in JavaScript provide a mechanism to create private variables by encapsulating them within a function scope. Here's how closures can be used to achieve this:
function createCounter() {let count = 0;return {increment: () => ++count,decrement: () => --count,getCount: () => count,};}const counter = createCounter();console.log(counter.increment()); // 1console.log(counter.getCount()); // 1console.log(counter.count); // undefined
Set
s and Map
s handle equality checks for objects?Set
s and Map
s in JavaScript determine the equality of objects based on reference equality, not by comparing their contents. This means objects are considered equal only if they point to the same memory location. For instance:
const set = new Set();const obj1 = { a: 1 };const obj2 = { a: 1 };set.add(obj1);set.add(obj2);console.log(set.size); // Output: 2
In this example, obj1
and obj2
are treated as separate entries in the Set because they are distinct objects, despite having identical properties. Therefore, Sets and Maps rely on object references to determine equality, not their internal values.
To access the index of an element in an array during iteration, you can utilize methods like forEach, map, for...of with entries, or a traditional for loop. Here's an example using forEach:
const array = ['a', 'b', 'c'];array.forEach((element, index) => {console.log(index, element);});
To determine the type of a variable in JavaScript, you use typeof
followed by the variable name. It returns a string indicating the variable's type: "string", "number", "boolean", "object", "function", "undefined", or "symbol". For arrays, use Array.isArray(variableName)
, and for null, check variableName === null
.
You've made it to the end of our extensive list of JavaScript interview questions and answers! We hope this guide has helped you gain the confidence and skills you need to ace your next JavaScript interview. Remember, practice is key, so keep coding and reviewing the concepts until they become second nature.