Answer: JavaScript interacts with browsers through the DOM (Document Object Model) API, allowing manipulation of HTML elements, handling events, and controlling browser behavior. For server interaction, JavaScript uses APIs like Fetch or XMLHttpRequest to make HTTP requests. With Node.js, JavaScript can also run on the server-side, handling HTTP requests, file operations, and database interactions directly.
Answer: A callback function is a function passed as an argument to another function, which is then invoked inside the outer function. Callbacks are essential for asynchronous operations because they allow you to specify what happens after an asynchronous task completes, without blocking execution.
// Example: Callback with setTimeout
function fetchData(callback) {
setTimeout(() => {
const data = { name: "John", age: 30 };
callback(data);
}, 1000);
}
fetchData((data) => {
console.log("Data received:", data);
});
console.log("This runs before the callback");
Answer: A closure is a function that has access to its own scope, the outer function's scope, and the global scope, even after the outer function has finished executing. Closures "remember" the environment in which they were created.
function createCounter() {
let count = 0; // Private variable
return function() {
count++; // Access to parent scope
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
Closures are useful for:
- Data encapsulation/private variables
- Function factories
- Event handlers and callbacks
- Implementing module patterns
4. What does this
keyword indicate in JavaScript? How does it behave differently in arrow functions vs regular functions?
Answer: this
refers to the execution context of a function:
-
In regular functions,
this
is dynamically determined by how the function is called:- In a method,
this
refers to the owner object - In a standalone function,
this
refers to the global object (or undefined in strict mode) - In an event,
this
refers to the element that received the event - With
call()
,apply()
, orbind()
,this
is explicitly set
- In a method,
-
In arrow functions,
this
is lexically scoped - it retains thethis
value of the enclosing context and cannot be changed, even withcall()
,apply()
, orbind()
.
// Regular function
const obj = {
name: "Object",
regularMethod: function() {
console.log(this.name); // "Object"
function innerFunction() {
console.log(this.name); // undefined (or window.name in non-strict mode)
}
innerFunction();
},
arrowMethod: function() {
console.log(this.name); // "Object"
const innerArrow = () => {
console.log(this.name); // "Object" - inherits this from parent scope
}
innerArrow();
}
};
Answer: Event bubbling is a mechanism where an event triggered on a nested element "bubbles up" through its ancestors in the DOM tree. When an event occurs on an element, it first runs the handlers on that element, then on its parent, then on other ancestors.
// Example:
document.getElementById("child").addEventListener("click", function(e) {
console.log("Child clicked");
// e.stopPropagation(); // Prevents bubbling
});
document.getElementById("parent").addEventListener("click", function() {
console.log("Parent clicked");
});
document.getElementById("grandparent").addEventListener("click", function() {
console.log("Grandparent clicked");
});
// Clicking on the child element logs:
// "Child clicked"
// "Parent clicked"
// "Grandparent clicked"
This enables event delegation, where you can attach a single event listener to a parent element to handle events for multiple child elements.
Answer: The Event Loop is a fundamental mechanism in JavaScript that handles asynchronous operations by continuously checking the message queue for new events/messages and executing associated callbacks.
How it works:
- Synchronous code executes on the call stack
- Asynchronous operations (setTimeout, promises, I/O, etc.) are delegated to the browser/Node.js APIs
- When these operations complete, their callbacks are added to the callback/task queue
- The event loop constantly checks if the call stack is empty
- If the stack is empty, it takes the first callback from the queue and pushes it onto the stack for execution
The event loop handles different types of tasks in specific order:
- Microtasks (Promises, queueMicrotask) are processed after the current task and before the next task
- Macrotasks (setTimeout, setInterval, I/O) are processed one per event loop iteration
console.log("Start");
setTimeout(() => {
console.log("Timeout callback");
}, 0);
Promise.resolve().then(() => {
console.log("Promise resolved");
});
console.log("End");
// Output:
// "Start"
// "End"
// "Promise resolved" (microtask runs before next macrotask)
// "Timeout callback"
Answer: Hoisting is JavaScript's default behavior of moving declarations to the top of their scope during the compilation phase, before code execution.
For variables:
var
declarations are hoisted and initialized withundefined
let
andconst
declarations are hoisted but not initialized (Temporal Dead Zone)
For functions:
- Function declarations are completely hoisted (both declaration and definition)
- Function expressions are not hoisted (if assigned to variables, follow variable hoisting rules)
// Variable hoisting
console.log(x); // undefined (hoisted but not initialized)
var x = 5;
console.log(y); // ReferenceError: y is not defined (in TDZ)
let y = 10;
// Function hoisting
sayHello(); // Works: "Hello" (function declaration is fully hoisted)
function sayHello() {
console.log("Hello");
}
sayBye(); // TypeError: sayBye is not a function
var sayBye = function() {
console.log("Bye");
};
Answer:
undefined
represents a variable that has been declared but not assigned a value, or a function that doesn't return anything, or attempting to access an object property that doesn't existnull
is an explicit assignment value representing "no value" or "empty" - it must be explicitly assigned
Key differences:
typeof undefined
is"undefined"
typeof null
is"object"
(a historical bug in JavaScript)undefined == null
istrue
(loose equality)undefined === null
isfalse
(strict equality)- Functions return
undefined
by default, notnull
let a;
console.log(a); // undefined
let b = null;
console.log(b); // null
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object"
console.log(undefined == null); // true
console.log(undefined === null); // false
Answer: JavaScript data types can be divided into primitive and reference types:
Primitive types: (immutable, stored by value)
String
: Text data -"Hello"
,'world'
,`template`
Number
: Integer and floating-point numbers -42
,3.14
Boolean
:true
orfalse
undefined
: Represents a declared but uninitialized variablenull
: Represents the intentional absence of any valueSymbol
: Unique and immutable primitive introduced in ES6BigInt
: For integers larger than Number can represent (ES2020)
Reference types: (mutable, stored by reference)
Object
: Key-value pairs -{}
,new Object()
Array
: Ordered collections -[]
,new Array()
Function
: Callable objects -function() {}
,() => {}
Date
: Date and time representation -new Date()
RegExp
: Regular expressions -/pattern/
,new RegExp()
Map
,Set
,WeakMap
,WeakSet
(ES6 collections)
Behavior differences:
- Primitives are compared by value
- Reference types are compared by reference (memory address)
// Primitives - compared by value
let a = 5;
let b = 5;
console.log(a === b); // true
// Reference types - compared by reference
let obj1 = { value: 5 };
let obj2 = { value: 5 };
console.log(obj1 === obj2); // false (different objects in memory)
let arr1 = [1, 2, 3];
let arr2 = arr1;
arr2.push(4);
console.log(arr1); // [1, 2, 3, 4] (both variables reference the same array)
Answer: Scope determines the accessibility of variables, functions, and objects during runtime.
Global Scope:
- Variables declared outside any function or block
- Accessible throughout the entire program
- Variables declared without
var
,let
, orconst
become global
Function Scope:
- Variables declared inside a function using
var
- Accessible only within the function
- Each function creates its own scope
Block Scope:
- Variables declared inside a block
{}
usinglet
orconst
- Accessible only within the block
- Introduced in ES6
// Global scope
var globalVar = "I'm global";
let globalLet = "I'm also global";
function exampleFunction() {
// Function scope
var functionVar = "I'm function-scoped";
let functionLet = "I'm block-scoped";
if (true) {
// Block scope
var blockVar = "I'm still function-scoped (var)";
let blockLet = "I'm block-scoped";
const blockConst = "I'm also block-scoped";
console.log(globalVar); // Accessible
console.log(functionVar); // Accessible
console.log(blockLet); // Accessible
}
console.log(blockVar); // Accessible (var ignores blocks)
console.log(blockLet); // ReferenceError (let respects blocks)
}
console.log(functionVar); // ReferenceError (function scope)
console.log(blockVar); // ReferenceError (function scope)
Answer: An array is an ordered, indexed collection of values in JavaScript. It can store elements of any type and dynamically resize.
Key array methods and differences:
map vs forEach:
map()
creates a new array by transforming each element and returns itforEach()
just iterates over elements without returning anything
const numbers = [1, 2, 3];
// map returns a new array
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6]
// forEach doesn't return anything
numbers.forEach(num => console.log(num)); // logs 1, 2, 3
find vs filter:
find()
returns the first element that satisfies the conditionfilter()
returns all elements that satisfy the condition in a new array
const people = [
{ name: "John", age: 25 },
{ name: "Jane", age: 30 },
{ name: "Jack", age: 30 }
];
// find returns the first match
const firstThirty = people.find(person => person.age === 30);
console.log(firstThirty); // { name: "Jane", age: 30 }
// filter returns all matches
const allThirty = people.filter(person => person.age === 30);
console.log(allThirty); // [{ name: "Jane", age: 30 }, { name: "Jack", age: 30 }]
slice vs splice:
slice()
returns a new array with selected elements without modifying the originalsplice()
modifies the original array by adding/removing elements
const array = [1, 2, 3, 4, 5];
// slice doesn't modify the original array
const sliced = array.slice(1, 3);
console.log(sliced); // [2, 3]
console.log(array); // [1, 2, 3, 4, 5] (unchanged)
// splice modifies the original array
const spliced = array.splice(1, 2, 'a', 'b');
console.log(spliced); // [2, 3] (removed elements)
console.log(array); // [1, 'a', 'b', 4, 5] (modified)
shift vs unshift:
shift()
removes the first element and returns itunshift()
adds elements to the beginning and returns the new length
const array = [2, 3, 4];
// shift removes from the beginning
const first = array.shift();
console.log(first); // 2
console.log(array); // [3, 4]
// unshift adds to the beginning
const newLength = array.unshift(1, 2);
console.log(newLength); // 4
console.log(array); // [1, 2, 3, 4]
Answer: ES6 (ECMAScript 2015) was a major update to JavaScript that introduced significant improvements and new features:
Arrow Functions:
- Shorter syntax for function expressions
- Lexical
this
binding (inheritsthis
from surrounding scope)
// Traditional function
function add(a, b) {
return a + b;
}
// Arrow function
const add = (a, b) => a + b;
Classes:
- Syntactic sugar over JavaScript's prototype-based inheritance
- Familiar OOP style with constructors, methods, inheritance
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
return `Hello, I'm ${this.name}`;
}
}
class Employee extends Person {
constructor(name, position) {
super(name);
this.position = position;
}
}
Modules:
- Native support for modular code organization
- Import/export syntax
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
// main.js
import { add, subtract } from './math.js';
import * as math from './math.js';
Other key ES6 features:
let
andconst
for block-scoped variables- Template literals with string interpolation
- Default parameters
- Rest and spread operators
- Destructuring assignment
- Enhanced object literals
- Promises for async programming
- Map and Set collections
- Symbols
- Iterators and generators
Answer:
Scope:
var
: Function-scoped or globally-scopedlet
andconst
: Block-scoped
Hoisting:
var
: Hoisted and initialized withundefined
let
andconst
: Hoisted but not initialized (Temporal Dead Zone)
Reassignment:
var
andlet
: Can be reassignedconst
: Cannot be reassigned after initialization
Redeclaration:
var
: Can be redeclared in the same scopelet
andconst
: Cannot be redeclared in the same scope
Initialization:
var
andlet
: Can be declared without initializationconst
: Must be initialized during declaration
// Scope
function example() {
if (true) {
var varVariable = 'var'; // Function-scoped
let letVariable = 'let'; // Block-scoped
const constVariable = 'const'; // Block-scoped
}
console.log(varVariable); // 'var'
console.log(letVariable); // ReferenceError
console.log(constVariable); // ReferenceError
}
// Hoisting
console.log(varVariable); // undefined
console.log(letVariable); // ReferenceError (TDZ)
var varVariable = 'var';
let letVariable = 'let';
// Reassignment
var a = 1;
a = 2; // OK
let b = 1;
b = 2; // OK
const c = 1;
c = 2; // TypeError: Assignment to constant variable
// Note: Objects assigned to const can have their properties modified
const obj = { prop: 1 };
obj.prop = 2; // OK
obj = {}; // TypeError
Answer: The spread operator (...
) expands an iterable (like an array or string) into individual elements, or an object into individual key-value pairs.
Array spreading:
// Combine arrays
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
// Clone an array
const original = [1, 2, 3];
const clone = [...original];
// Convert string to array of characters
const chars = [..."hello"]; // ['h', 'e', 'l', 'l', 'o']
// Use with function arguments
const numbers = [1, 2, 3];
const sum = Math.max(...numbers); // Same as Math.max(1, 2, 3)
Object spreading:
// Merge objects
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 }; // b will be overwritten
const merged = { ...obj1, ...obj2 }; // { a: 1, b: 3, c: 4 }
// Clone an object
const original = { a: 1, b: 2 };
const clone = { ...original };
// Override specific properties
const user = { name: 'John', age: 30 };
const updatedUser = { ...user, age: 31 }; // { name: 'John', age: 31 }
Answer: The prototype chain is JavaScript's inheritance mechanism. Every JavaScript object has a hidden property called [[Prototype]]
(accessed via __proto__
or Object.getPrototypeOf()
) that points to its prototype object. When a property or method is accessed on an object, JavaScript first looks for it directly on the object. If not found, it looks on the object's prototype, then that prototype's prototype, and so on, forming a "chain" of prototypes.
// Creating an object with a prototype
const animal = {
eats: true,
walk() {
console.log("Animal walks");
}
};
const rabbit = Object.create(animal);
rabbit.jumps = true;
console.log(rabbit.jumps); // true (own property)
console.log(rabbit.eats); // true (inherited from animal)
rabbit.walk(); // "Animal walks" (method from animal)
// Checking the prototype chain
console.log(rabbit.__proto__ === animal); // true
console.log(animal.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null (end of chain)
The prototype chain works with constructor functions and classes too:
// Constructor function
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name} is eating`);
};
function Rabbit(name) {
Animal.call(this, name);
this.jumps = true;
}
// Setting up inheritance
Rabbit.prototype = Object.create(Animal.prototype);
Rabbit.prototype.constructor = Rabbit;
Rabbit.prototype.jump = function() {
console.log(`${this.name} is jumping`);
};
const rabbit = new Rabbit("Bugs");
rabbit.eat(); // "Bugs is eating" (inherited)
rabbit.jump(); // "Bugs is jumping" (own)
Answer: In JavaScript, classes and objects are related but distinct concepts:
Class:
- A blueprint or template for creating objects
- Defines properties and methods that objects will have
- Introduced in ES6 as syntactic sugar over prototype-based inheritance
- Cannot be used directly (must be instantiated)
- Uses the
class
keyword, has aconstructor
, and can extend other classes
Object:
- An instance of a class or a standalone entity
- Has actual data (property values) and behavior (methods)
- Can be created using object literals, constructors,
Object.create()
, or class instantiation - Can be used and manipulated directly
// Class definition (blueprint)
class Car {
constructor(make, model) {
this.make = make;
this.model = model;
this.speed = 0;
}
accelerate(amount) {
this.speed += amount;
return this.speed;
}
brake(amount) {
this.speed = Math.max(0, this.speed - amount);
return this.speed;
}
}
// Objects (instances of Car class)
const car1 = new Car('Toyota', 'Corolla');
const car2 = new Car('Honda', 'Civic');
console.log(car1.make); // 'Toyota'
console.log(car2.make); // 'Honda'
console.log(car1.accelerate(20)); // 20
console.log(car2.accelerate(15)); // 15
// Standalone object (not class-based)
const car3 = {
make: 'Ford',
model: 'Mustang',
speed: 0,
accelerate(amount) {
this.speed += amount;
return this.speed;
}
};
Answer: JavaScript uses different parameter passing mechanisms depending on data types:
Call by Value (Primitive Types):
- When passing primitive types (strings, numbers, booleans, etc.)
- A copy of the value is passed to the function
- Changes to the parameter inside the function don't affect the original value
function incrementValue(x) {
x = x + 1;
console.log("Inside function:", x);
}
let num = 5;
incrementValue(num); // Inside function: 6
console.log("Original:", num); // Original: 5 (unchanged)
Call by Reference (Objects and Arrays):
- When passing objects or arrays
- A reference to the memory location is passed
- Changes to the object's properties or array's elements inside the function affect the original
function addProperty(obj) {
obj.newProp = "Added";
}
function reassignObject(obj) {
obj = { reassigned: true }; // This creates a new object
console.log("Inside function:", obj);
}
let myObj = { original: true };
addProperty(myObj);
console.log(myObj); // { original: true, newProp: "Added" } (modified)
reassignObject(myObj);
console.log(myObj); // { original: true, newProp: "Added" } (unchanged)
Important Note:
- JavaScript is technically always "pass by value", but when the value is a reference (as with objects), it behaves like "pass by reference" for the object's contents
- Reassigning the parameter variable inside a function doesn't affect the original reference
Answer: A higher-order function is a function that either:
- Takes one or more functions as arguments, or
- Returns a function as its result
Higher-order functions enable powerful functional programming patterns like composition, abstraction, and reusability.
Examples:
- Functions that accept functions as arguments:
// Array methods like map, filter, reduce
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(num => num * 2);
// Custom higher-order function
function repeat(n, action) {
for (let i = 0; i < n; i++) {
action(i);
}
}
repeat(3, console.log); // Logs: 0, 1, 2
- Functions that return functions:
// Function factory
function multiply(factor) {
return function(number) {
return number * factor;
};
}
const double = multiply(2);
const triple = multiply(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// Currying
const add = a => b => a + b;
const add5 = add(5);
console.log(add5(3)); // 8
Benefits of higher-order functions:
- Code reusability
- Abstraction (hide implementation details)
- Composition (building complex functions from simpler ones)
- Creating domain-specific language constructs
- Enabling functional programming patterns
Answer: Object-Oriented Programming is a programming paradigm based on the concept of "objects" that contain data (properties) and code (methods). JavaScript supports OOP through prototypes, classes, and objects.
Main principles of OOP:
- Encapsulation:
- Bundling data and methods that operate on that data within a single unit (object)
- Information hiding (private/public members)
class BankAccount {
#balance = 0; // Private field (ES2020+)
constructor(owner) {
this.owner = owner;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
return true;
}
return false;
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount("John");
account.deposit(100);
console.log(account.getBalance()); // 100
// console.log(account.#balance); // SyntaxError (private field)
- Inheritance:
- Creating new classes (child classes) based on existing ones (parent classes)
- Child classes inherit properties and methods from parent classes
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // Call parent constructor
this.breed = breed;
}
speak() {
console.log(`${this.name} barks`); // Override parent method
}
}
const dog = new Dog("Rex", "German Shepherd");
dog.speak(); // "Rex barks"
- Polymorphism:
- Objects of different classes can be treated as objects of a common superclass
- Method overriding (redefining methods in child classes)
class Shape {
area() {
return 0;
}
}
class Circle extends Shape {
constructor(radius) {
super();
this.radius = radius;
}
area() {
return Math.PI * this.radius * this.radius;
}
}
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
area() {
return this.width * this.height;
}
}
// Polymorphism in action
function calculateArea(shape) {
return shape.area();
}
const shapes = [new Circle(5), new Rectangle(4, 5)];
shapes.forEach(shape => {
console.log(`Area: ${calculateArea(shape)}`);
});
- Abstraction:
- Hiding complex implementation details and showing only the necessary features
- Using interfaces, abstract classes, or simply omitting internal details
// Abstraction through interface consistency
class Vehicle {
constructor(make, model) {
this.make = make;
this.model = model;
}
start() {
throw new Error("Method 'start' must be implemented");
}
stop() {
throw new Error("Method 'stop' must be implemented");
}
}
class Car extends Vehicle {
start() {
return "Car engine started";
}
stop() {
return "Car engine stopped";
}
}
// User doesn't need to know how the car starts internally
const myCar = new Car("Toyota", "Corolla");
console.log(myCar.start()); // "Car engine started"