80% of JavaScript interviews Flashcards
What is the difference between var, let, and const?
In JavaScript, var
, let
, and const
are used to declare variables, but they differ in scope, hoisting, and mutability:
-
var:
- Scope: Function-scoped or globally scoped if declared outside a function. Not block-scoped.
-
Hoisting: Variables are hoisted to the top of their function or global scope and initialized with
undefined
, allowing use before declaration. - Reassignment: Can be reassigned and redeclared within the same scope.
-
Example:
javascript var x = 5; var x = 10; // Redeclaration allowed console.log(x); // 10
-
let:
-
Scope: Block-scoped (limited to the block
{}
where it’s defined). - Hoisting: Hoisted to the top of the block but not initialized, causing a “Temporal Dead Zone” (TDZ) until the declaration is reached.
- Reassignment: Can be reassigned but cannot be redeclared in the same scope.
-
Example:
javascript let y = 5; y = 10; // Reassignment allowed // let y = 15; // Error: redeclaration not allowed console.log(y); // 10
-
Scope: Block-scoped (limited to the block
-
const:
-
Scope: Block-scoped, like
let
. - Hoisting: Hoisted but not initialized, also subject to TDZ.
- Reassignment: Cannot be reassigned or redeclared after initialization. However, for objects and arrays, their properties or elements can be modified.
-
Example:
javascript const z = 5; // z = 10; // Error: reassignment not allowed const obj = { a: 1 }; obj.a = 2; // Allowed: modifying object property console.log(obj); // { a: 2 }
-
Scope: Block-scoped, like
Key Differences:
- Use var
for legacy code (pre-ES6), but it’s prone to errors due to hoisting and lack of block scope.
- Use let
for variables that need reassignment.
- Use const
for variables that should not be reassigned, promoting immutability.
As an SDET, I’d ensure tests cover scenarios like variable scope, hoisting behavior, and immutability to catch bugs related to incorrect usage.
Explain hoisting in JavaScript with examples.
Your Response:
“Hoisting is a JavaScript behavior where variable and function declarations are moved to the top of their scope—either global or function scope—during compilation, but initializations stay in place.
For example, with var
:
console.log(x); // Outputs undefined var x = 5;//⬅️ initializations
Here, var x
is hoisted and initialized as undefined
, so it’s accessible but not yet assigned.
With let
or const
, it’s different:
console.log(y); // ReferenceError let y = 10;
let y
is hoisted but not initialized, creating a Temporal Dead Zone until the declaration, so accessing it early throws an error.
Function declarations are fully hoisted:
sayHello(); // Outputs "Hello!" function sayHello() { console.log("Hello!"); }
The entire function is available before its declaration.
As an SDET, I’d write tests to catch hoisting-related bugs, like accessing variables before initialization, and recommend using let
or const
to avoid var
’s unpredictable hoisting behavior.”
Tips for Delivery:
1. Be Concise: This response is short (~1 minute) but covers the essentials. Avoid over-explaining unless prompted.
2. Use Examples: Briefly show code or describe scenarios to prove you understand the concept practically.
3. Relate to SDET Role: Mention testing or code quality to tie it to your expertise, showing you think like a quality engineer.
4. Stay Confident: Speak clearly and pause slightly after key points to ensure clarity.
5. Adapt to Follow-Ups: If asked for more details (e.g., function expressions or TDZ), expand with:
- “Function expressions, like var func = function() {}
, only hoist the variable, not the function, so calling it early causes a TypeError
.”
- “The Temporal Dead Zone ensures let
and const
are safer by preventing access before initialization.”
Practice Version for Confidence:
Practice this a few times to sound natural:
“Hoisting moves declarations to the top of their scope. For var
, it’s initialized as undefined
, like console.log(x); var x = 5;
logging undefined
. For let
or const
, it’s hoisted but in a Temporal Dead Zone, so console.log(y); let y = 10;
throws a ReferenceError
. Functions hoist fully, so sayHello(); function sayHello() {}
works. As an SDET, I’d test these edge cases to catch bugs and prefer let
/const
for safer code.”
This keeps you sounding professional, relevant, and ready for deeper questions!
What are closures in JavaScript?
“A closure in JavaScript is when a function retains access to variables from its outer (enclosing) scope, even after that outer scope has finished executing. It’s like a function carrying a ‘backpack’ of its surrounding variables.
For example:
function outer() { let count = 0; return function inner() { count++; console.log(count); }; } const counter = outer(); counter(); // Outputs 1 counter(); // Outputs 2
Here, inner
forms a closure over count
. Even after outer
finishes, inner
remembers and updates count
each time it’s called.
Closures are useful for data privacy, like creating private variables, or for things like event handlers and callbacks. As an SDET, I’d test closures to ensure variables maintain their state correctly and there are no memory leaks from unintended references.”
Why This Works:
- Clear and Concise: Explains the concept in ~30-40 seconds with a simple analogy (“backpack”).
- Practical Example: The counter example shows how closures work in real code.
- SDET Relevance: Ties it to testing, showing you understand practical implications.
- Engaging: Invites follow-up questions (e.g., memory leaks, use cases) without overloading.
If Prompted for More:
- Use Case: “Closures are great for creating factory functions or encapsulating state, like in module patterns.”
- Testing Angle: “I’d write tests to verify the inner function accesses the correct outer variables and check for memory issues in long-running apps.”
Explain the difference between shallow copy and deep copy.
“A shallow copy creates a new object or array, but only copies the top-level properties or elements. If those properties are objects or arrays, it copies their references, not the nested data. So, changes to nested objects affect both the original and the copy.
A deep copy, on the other hand, creates a completely independent copy of the object, including all nested objects and arrays. Changes to the copy don’t affect the original.
For example:
// Shallow copy const original = { name: 'Alice', details: { age: 25 } }; const shallow = { ...original }; shallow.details.age = 30; console.log(original.details.age); // Outputs 30 (original is affected) // Deep copy const deep = JSON.parse(JSON.stringify(original)); deep.details.age = 35; console.log(original.details.age); // Outputs 30 (original is unchanged)
Shallow copies are faster but risk unintended side effects with nested data. Deep copies ensure independence but can be slower for large objects. As an SDET, I’d test shallow vs. deep copy behavior to ensure data integrity and catch bugs from shared references.”
Why This Works:
- Clear Definition: Distinguishes shallow and deep copies concisely.
- Practical Example: Shows the difference with code, making it tangible.
- SDET Tie-In: Highlights testing for data integrity, relevant to the role.
- Concise: Delivers in ~40 seconds, leaving room for follow-ups.
If Prompted for More:
- Shallow Copy Methods: “Shallow copies can be made with Object.assign()
, spread operator (...
), or Array.slice()
.”
- Deep Copy Limitations: “JSON-based deep copies don’t handle functions or special objects like Dates. Libraries like Lodash offer robust deep cloning.”
- Testing Angle: “I’d write unit tests to verify that deep copies don’t mutate the original and check performance for large datasets.”
What is prototypal inheritance, and how does it work?
“Prototypal inheritance is JavaScript’s mechanism for objects to inherit properties and methods from other objects via their prototype chain. Every object has a __proto__ property linking to its prototype, which allows it to access properties or methods defined on that prototype.
For example:
const parent = { greet: () => console.log('Hello') }; const child = Object.create(parent); child.greet(); // Outputs 'Hello' Here, child inherits greet from parent through the prototype chain. If a property isn’t found on child, JavaScript looks up the chain.
As an SDET, I’d test prototype methods to ensure they behave as expected and verify the chain isn’t unintentionally modified, which could break inheritance.”
What is the difference between Object.create() and class-based inheritance?
“Object.create() is a method to create a new object with a specified prototype, enabling prototypal inheritance directly. Class-based inheritance, introduced in ES6, uses the class syntax to define a blueprint for objects, but under the hood, it still uses prototypes.
For example:
// Object.create const parent = { greet: () => 'Hello' }; const child = Object.create(parent); console.log(child.greet()); // Hello // Class-based class Parent { greet() { return 'Hello'; } } class Child extends Parent {} const child2 = new Child(); console.log(child2.greet()); // Hello
Object.create() is lightweight and explicit for linking prototypes, while class offers a structured, syntactic sugar approach for inheritance. As an SDET, I’d test both to ensure inherited methods work and check for proper prototype chain behavior.”
What are getter and setter functions in JavaScript?
“Getters and setters are special methods in JavaScript that allow controlled access to an object’s properties. A getter retrieves a property’s value, while a setter defines how a property is set, often with validation.
For example:
const person = { _age: 25, get age() { return this._age; }, set age(value) { if (value >= 0) this._age = value; } }; console.log(person.age); // 25 person.age = 30; // Sets _age to 30 person.age = -5; // Ignored due to validation
As an SDET, I’d test getters to ensure they return correct values and setters to verify validation logic prevents invalid updates.”
How does this keyword work in JavaScript?
“The this keyword in JavaScript refers to the object that is executing the current function, and its value depends on how the function is called.
For example:
Copy const obj = { name: 'Alice', greet: function() { console.log(this.name); } }; obj.greet(); // Outputs 'Alice' (this is obj) const func = obj.greet; func(); // Outputs undefined (this is global/window in non-strict mode)
this can also be set explicitly with call(), apply(), or bind(). In arrow functions, this is lexically bound to the enclosing scope. As an SDET, I’d test this behavior across contexts, like event handlers or callbacks, to catch bugs from incorrect bindings.”
What is the difference between call(), apply(), and bind()?
call(), apply(), and bind()are methods to control the value of this in a function and pass arguments.
call(): Invokes the function immediately, setting this and passing arguments individually.
apply(): Like call(), but passes arguments as an array.
bind(): Returns a new function with this permanently set, without invoking it immediately.
For example:
function greet(greeting) { console.log(`${greeting}, ${this.name}`); } const person = { name: 'Alice' }; greet.call(person, 'Hi'); // Outputs: Hi, Alice greet.apply(person, ['Hello']); // Outputs: Hello, Alice const boundGreet = greet.bind(person); boundGreet('Hey'); // Outputs: Hey, Alice
As an SDET, I’d test these methods to ensure this is correctly bound and arguments are passed as expected, especially in complex callback scenarios.”
What is the difference between == and === in JavaScript?
“== is the loose equality operator, which compares values after performing type coercion, converting operands to the same type.
===is the strict equality operator, which compares both value and type without coercion.
For example:
console.log(5 == '5'); // true (string '5' coerced to number) console.log(5 === '5'); // false (different types)
As an SDET, I’d test edge cases with == to catch bugs from unexpected coercion, like null == undefined being true, and advocate for === to ensure predictable behavior.”
How does the event loop work in JavaScript?
- How does the event loop work in JavaScript?
Response:
“The event loop is JavaScript’s mechanism for handling asynchronous operations in its single-threaded environment. It continuously checks the call stack and task queue. If the call stack is empty, it takes tasks from the queue (like callbacks or async operations) and pushes them to the stack for execution.
For example:
console.log('Start'); setTimeout(() => console.log('Timeout'), 0); console.log('End'); // Outputs: Start, End, Timeout
Even with a 0ms delay, setTimeout goes to the task queue, and the event loop processes it after the synchronous code.
As an SDET, I’d test async code to ensure callbacks execute in the correct order and verify no race conditions occur.”
What are promises and how do they work?
“A promise is an object representing the eventual completion or failure of an asynchronous operation. It has three states: pending, fulfilled, or rejected. Promises allow chaining with .then() for success and .catch() for errors.
For example:
const promise = new Promise((resolve, reject) => { setTimeout(() => resolve('Success'), 1000); }); promise.then(result => console.log(result)); // Outputs 'Success' after 1s promise.catch(error => console.log(error));
As an SDET, I’d write tests to verify promise resolution, error handling, and chaining behavior to ensure robust async workflows.”
What is async/await, and how is it different from promises?
“async/await is syntactic sugar built on top of promises to make asynchronous code look synchronous and easier to read. An async function returns a promise, and await pauses execution until the promise resolves.
For example:
async function fetchData() { try { const result = await new Promise(resolve => setTimeout(() => resolve('Data'), 1000)); console.log(result); // Outputs 'Data' after 1s } catch (error) { console.log(error); } } fetchData();
Compared to promises, async/await avoids chaining .then() and makes error handling cleaner with try/catch. As an SDET, I’d test await behavior for correct resolution and ensure errors are caught properly.”
What is memoization, and how can it be implemented in JavaScript?
“Memoization is an optimization technique where the results of expensive function calls are cached and reused when the same inputs occur again, reducing computation time.
For example:
function memoize(fn) { const cache = {}; return function (...args) { const key = JSON.stringify(args); if (cache[key]) return cache[key]; cache[key] = fn(...args); return cache[key]; }; } const slowFunction = (num) => { // Simulate expensive computation return num * 2; }; const memoized = memoize(slowFunction); console.log(memoized(5)); // Computes and caches: 10 console.log(memoized(5)); // Uses cache: 10
As an SDET, I’d test memoized functions to ensure cache hits return correct results and verify performance improvements for repeated calls.”
What are higher-order functions, and can you give an example?
“A higher-order function is a function that either takes another function as an argument, returns a function, or both. They’re powerful for abstraction and code reuse.
For example:
const numbers = [1, 2, 3]; const double = num => num * 2; const doubledNumbers = numbers.map(double); // map is a higher-order function console.log(doubledNumbers); // Outputs [2, 4, 6]
Here, map takes the double function as an argument. As an SDET, I’d test higher-order functions to ensure the passed functions produce correct outputs and handle edge cases like empty arrays.”
Delivery Tips for the Interview:
Be Concise: Each answer is ~30-40 seconds, fitting well in an interview setting.
Use Examples: Brief code snippets make your answers concrete and demonstrate understanding.
SDET Focus: Mention testing (e.g., edge cases, performance, error handling) to align with the role.
Stay Confident: Practice these to deliver smoothly. Pause slightly after key points for clarity.
Handle Follow-Ups: If asked for more, expand briefly (e.g., “For promises, I could also test race conditions with Promise.all”).
Practice Strategy:
Rehearse each answer aloud 2-3 times to sound natural.
Focus on one question at a time, then combine them to simulate a rapid-fire Q&A.
If nervous, start with a simple structure: “It’s [definition]. For example, [code]. As an SDET, I’d test [relevance].”
These responses should help you shine in an SDET interview by showing technical depth and testing awareness!
Delivery Tips for the Interview:
Be Concise: Each answer is ~30-40 seconds, fitting well in an interview setting.
Use Examples: Brief code snippets make your answers concrete and demonstrate understanding.
SDET Focus: Mention testing (e.g., edge cases, performance, error handling) to align with the role.
Stay Confident: Practice these to deliver smoothly. Pause slightly after key points for clarity.
Handle Follow-Ups: If asked for more, expand briefly (e.g., “For promises, I could also test race conditions with Promise.all”).
Practice Strategy:
Rehearse each answer aloud 2-3 times to sound natural.
Focus on one question at a time, then combine them to simulate a rapid-fire Q&A.
If nervous, start with a simple structure: “It’s [definition]. For example, [code]. As an SDET, I’d test [relevance].”
These responses should help you shine in an SDET interview by showing technical depth and testing awareness!