JavaScript Flashcards

1
Q

What is the difference between == and === in JavaScript?

A

Triple equals is strict, meaning that a string with the value of true, will not equal a boolean with true.

Double equals is a general equation.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

What are the different types of scope in JavaScript?

A

Global Scope
- Variables declared outside of any function or block are in the global scope.

Function Scope
- Variables declared with var inside a function are scoped to that function.

Block Scope
- Variables declared with let or const are scoped to the nearest enclosing {} block.

Lexical Scope
- Scope is determined by the physical location of code in the source—i.e., how functions are nested.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

How does hoisting work with var, let, and const?

A

Hoisting is a behavior in JavaScript where variable and function declarations are moved to the top of their scope (before code execution), but only the declarations are hoisted—not initializations.

  • var declarations are hoisted to the top of their function or global scope
  • let and const are also hoisted to the top of their block scope, but they are not initialized
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

What are closures and how are they used in real-world scenarios?

A

Closures let functions access variables from their outer scope even after that scope has finished running.

They’re used for private state, callbacks, function factories, and memoization.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Explain the difference between function declarations and arrow functions.

A

Function declarations are hoisted, meaning you can call them before they appear in the code, and they have their own this context. Arrow functions are not hoisted and use the this value from the surrounding (lexical) scope. Arrow functions also don’t have their own arguments object or super.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

How does the JavaScript event loop work?

A

The event loop is what allows JavaScript (which is single-threaded) to handle asynchronous operations like setTimeout, promises, or DOM events. It works by pulling tasks from the call stack and message queue (or microtask queue) in order, ensuring non-blocking behavior.

Synchronous code runs first, then microtasks (like .then()), and finally macrotasks (like setTimeout) are processed in the order they arrive.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

What are call stack, event queue, and microtask queue?

A

The call stack is a data structure that keeps track of function calls in a program. When a function is invoked, it’s added to the stack, and once it finishes, it’s removed.

The event queue (or task queue) holds messages like user events or setTimeout callbacks that are waiting to be processed by the JavaScript runtime. The event loop takes tasks from this queue and pushes them onto the call stack when it’s empty.

The microtask queue is similar but prioritized higher than the event queue. It contains tasks like Promise callbacks and MutationObserver, which are executed right after the current task completes and before the next event from the event queue is processed.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

What is memory leak in JavaScript and how can you prevent it?

A

A memory leak in JavaScript occurs when a program holds onto memory it no longer needs, preventing the garbage collector from reclaiming it. This can lead to performance issues as memory usage grows over time. Common causes include forgotten timers, closures holding references, global variables, and detached DOM elements.

To prevent memory leaks, avoid using unnecessary global variables, clear timers or intervals when they’re no longer needed, break circular references, and make sure to remove event listeners and DOM nodes properly when they’re not in use. Using tools like Chrome DevTools can help detect and diagnose memory leaks.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

How does garbage collection work in JavaScript?

A

Garbage collection in JavaScript is an automatic process that reclaims memory used by objects no longer needed by the program. The most common strategy is mark-and-sweep, where the garbage collector marks all reachable objects starting from roots (like global variables), then sweeps away everything not marked.

An object is considered reachable if it can be accessed directly or indirectly from the roots. If it’s not reachable, it’s assumed to be no longer needed and is removed from memory. Developers don’t manage memory directly but can write cleaner code to help the garbage collector do its job efficiently.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

What’s the difference between synchronous and asynchronous code execution?

A

Synchronous code is executed line by line, where each operation waits for the previous one to complete before moving on. This can block the main thread if an operation takes a long time, like a network request or heavy computation.

Asynchronous code, on the other hand, allows certain tasks to run in the background without blocking the main thread. It uses mechanisms like callbacks, promises, and async/await to handle operations that take time, enabling smoother and more responsive programs

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

What is prototypal inheritance, and how is it different from classical inheritance?

A

Prototypal inheritance in JavaScript means objects can inherit properties and methods directly from other objects using a prototype chain. Every object has an internal link to another object called its prototype, and it can access properties from it if they’re not found on itself.

Classical inheritance, common in languages like Java or C++, uses classes and inheritance hierarchies where instances are created from class blueprints. In contrast, prototypal inheritance is more flexible, allowing dynamic extension and object-to-object inheritance without needing classes, though ES6 introduced class syntax to mimic classical inheritance.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

How do Object.create() and Object.assign() work?

A

Object.create() creates a new object with the specified prototype object and optionally adds properties. It’s commonly used for setting up inheritance by directly linking one object to another as its prototype.

Object.assign() copies enumerable properties from one or more source objects to a target object. It’s often used for shallow cloning or merging objects, but it does not copy non-enumerable properties or deep structures like nested objects.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

What is the difference between deep copy and shallow copy in JavaScript?

A

A shallow copy creates a new object but only copies references for nested objects or arrays, not their actual content. So if the original object has nested structures, changes to those will reflect in the copy.

A deep copy duplicates everything, including nested objects and arrays, creating entirely independent copies. This means changes to the original object or its nested elements won’t affect the deep copy. Deep copies can be made using methods like structuredClone() or libraries like Lodash’s cloneDeep().

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

What are the different ways to clone an object in JavaScript?

A

You can clone an object in JavaScript using Object.assign({}, obj) or the spread operator { …obj } for a shallow copy. For a deep copy, you can use structuredClone(obj), JSON.parse(JSON.stringify(obj)) (with limitations), or libraries like Lodash’s _.cloneDeep(). Deep copies handle nested structures, while shallow copies only duplicate the top level.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

How can you flatten a deeply nested array without using .flat()?

A

To flatten a deeply nested array without using .flat(), you can use a recursive function. The function iterates over each element in the array, and if it finds a nested array, it recursively flattens it.

Non-array items are directly added to the result. This method works for arrays of any depth and allows for full control over the flattening process.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

What’s the difference between async/await and .then()?

A

The primary difference between async/await and .then() lies in how they handle asynchronous code.

Async/await allows you to write asynchronous code in a synchronous-like manner, making it more readable and easier to debug. You define an async function, and use await to pause execution until a Promise resolves.

.then() is a method used with Promises. It chains callbacks to handle the result of a Promise when it resolves. While it works fine, the chaining can become harder to manage with complex or multiple async operations (callback hell).

17
Q

How does error handling work with async/await?

A

Error handling with async/await is done using try/catch blocks. When using await, if a Promise is rejected, it will throw an error, which can be caught in the catch block. This makes the code look more like synchronous error handling and is easier to manage.

18
Q

What is the difference between Promise.all, Promise.allSettled, and Promise.race?

A
  • Promise.all waits for all promises to resolve, and returns an array of results. If any promise rejects, the entire operation fails immediately with that rejection.
  • Promise.allSettled waits for all promises to either resolve or reject, returning an array with the status (resolved or rejected) of each promise, regardless of their outcomes.
  • Promise.race returns the result of the first promise that resolves or rejects. Whichever promise settles first will determine the result, and the other promises are ignored.

Each method is useful depending on whether you need all promises to succeed, track all results, or just care about the first result.

19
Q

How can you create a custom Promise wrapper?

A

You can use this customPromise like a regular Promise, providing an executor function to define how to resolve or reject it. The custom wrapper allows you to add extra functionality, such as logging, timeouts, or custom behavior before resolving or rejecting the promise.

20
Q

What are the common pitfalls of using async functions inside loops?

A

Using async functions inside loops can lead to several common pitfalls:

  • Concurrency issues: By default, async functions inside loops run concurrently, which can lead to race conditions or unexpected results if the order of execution matters. You might expect each iteration to complete sequentially, but they may run out of order.
  • Promise handling: When using async functions in a loop, each iteration returns a promise, but if you don’t handle them properly (e.g., using await or Promise.all()), it can lead to unhandled promises, resulting in unexpected behavior or unhandled rejections.
  • Performance problems: If you have a large number of iterations, running async functions in parallel can cause performance bottlenecks, especially if you’re making network requests or database queries. Limiting concurrency or using Promise.all() carefully can help mitigate this.

To avoid these pitfalls, consider using for…of with await for sequential execution or Promise.all() for concurrent execution with proper error handling.

21
Q

What are the key differences between var, let, and const?

A

The key differences between var, let, and const are in their scoping and reassignment rules. var has function-level scope, while let and const are block-scoped. var is hoisted and initialized with undefined, whereas let and const are hoisted but not initialized until their declaration, causing a “temporal dead zone.”

Additionally, const prevents reassignment, while let allows it, and var can be reassigned as well.

22
Q

Explain destructuring and its benefits with examples.

A

Destructuring is a feature in JavaScript that allows you to unpack values from arrays or objects into distinct variables. It makes your code cleaner, more readable, and reduces the need for repetitive assignments.

Example for arrays:

const arr = [1, 2, 3];
const [a, b] = arr; // a = 1, b = 2

Example for objects:

const obj = { name: ‘John’, age: 25 };
const { name, age } = obj; // name = ‘John’, age = 25

The benefits of destructuring include simplifying complex data access, reducing code verbosity, and making it easier to extract specific properties from arrays or objects.

23
Q

What are template literals and how do they work?

A

Template literals in JavaScript allow you to create strings with embedded expressions and support multi-line strings. They are enclosed in backticks (`) and allow variables or expressions to be inserted using ${} syntax. For example, you can easily include values in a string: const greeting = Hello, ${name}!``. This feature makes string manipulation more readable and less error-prone compared to traditional string concatenation.

24
Q

What are rest and spread operators, and how are they different?

A

The rest operator (…) is used to collect multiple values into a single array, typically in function parameters. It allows you to handle an arbitrary number of arguments.

The spread operator is used to unpack elements from an array or object, spreading them into individual items. While the rest operator gathers values, the spread operator disperses them, such as in copying or merging arrays.

25
What is a Symbol and when would you use one?
A Symbol in JavaScript is a primitive data type used to create unique, immutable identifiers. Each symbol is guaranteed to be unique, even if the description is the same, which makes it useful for cases where you need unique property keys in objects. You might use Symbols when you want to avoid name clashes, like defining private properties or creating well-defined object keys that won't accidentally be overridden. For example, Symbols are often used in object-oriented design or when working with libraries or frameworks that require unique identifiers.
26
What are the most common security issues in JavaScript (XSS, CSRF, etc.)?
Some of the most common security issues in JavaScript include XSS (Cross-Site Scripting) and CSRF (Cross-Site Request Forgery): * XSS occurs when an attacker injects malicious scripts into web pages viewed by other users. It can lead to the execution of harmful code in the user's browser, potentially stealing cookies or redirecting to malicious sites. * CSRF exploits a user's authenticated session to send unauthorized requests to a web application. By tricking the user into submitting a request (e.g., clicking on a malicious link), attackers can perform actions on behalf of the user without their consent. Other security risks include Insecure Direct Object References (IDOR) and SQL Injection when improperly handling user input. To mitigate these risks, always sanitize inputs, use secure HTTP headers, and implement proper authentication and authorization practices.
27
How can you prevent or mitigate XSS attacks in frontend code?
To prevent XSS attacks in frontend code, always sanitize and escape user input before displaying it on the page. Avoid using inline JavaScript and instead bind event listeners securely. Implement a Content Security Policy (CSP) to restrict script sources and prevent malicious scripts from running. Additionally, ensure dynamic content is properly encoded to handle special characters like < and >.
28
What’s the difference between eval() and Function() constructor, and why should you avoid them?
Both eval() and the Function() constructor execute strings of code, but they differ in scope and security risks. eval() executes code in the current scope, which can lead to variable conflicts and unintended behavior, while Function() creates a new function in the global scope. You should avoid both because they can introduce security vulnerabilities, such as code injection attacks, and make your code harder to debug and maintain. They also hinder performance, as the JavaScript engine cannot optimize dynamically executed code effectively.
29
How would you safely parse and handle user input in vanilla JS?
To safely parse and handle user input in vanilla JavaScript, always sanitize the input by removing any harmful characters, and escape special characters when displaying it in HTML to prevent XSS attacks. Validate input using regular expressions to ensure it matches the expected format, and use prepared statements or parameterized queries when interacting with a server to prevent SQL injection. By following these practices, you can ensure user input is processed securely.
30
What are some common JavaScript code smells and how can you refactor them?
Common JavaScript code smells include duplicate code, which can be refactored by creating reusable functions, and large functions, which should be broken down into smaller, single-purpose ones. Callback hell can be refactored by using Promises or async/await for better readability. Overusing global variables can be avoided by encapsulating them within functions or using modules to limit their scope.