# Javascript Interview - Advanced

1. **<mark>What is Callback Hell and How to avoid this?</mark>**  
    **Callback Hell** (also known as the “Pyramid of Doom”) refers to a situation in JavaScript (or other asynchronous programming environments) where multiple nested callbacks make code hard to read, understand, and maintain.
    
    ```javascript
    doSomething(function(result1) {
        doSomethingElse(result1, function(result2) {
            doAnotherThing(result2, function(result3) {
                doFinalThing(result3, function(result4) {
                    // and so on...
                });
            });
        });
    });
    ```
    
    This kind of code grows horizontally and becomes difficult to manage because: It’s hard to read.
    
    Error handling becomes messy. Logic is tightly coupled.  
    **How to Avoid Callback Hell -**  
    i) Use Named Functions  
    Instead of writing anonymous functions inside callbacks, define them separately
    
    ```javascript
    function handleResult1(result1) {
        doSomethingElse(result1, handleResult2);
    }
    
    function handleResult2(result2) {
        doAnotherThing(result2, handleResult3);
    }
    
    function handleResult3(result3) {
        doFinalThing(result3, finalCallback);
    }
    
    doSomething(handleResult1);
    ```
    
    ii) Use Promises
    
    Promises flatten the structure and make the flow easier to follow
    
    ```javascript
    doSomething()
        .then(result1 => doSomethingElse(result1))
        .then(result2 => doAnotherThing(result2))
        .then(result3 => doFinalThing(result3))
        .catch(error => console.error(error));
    ```
    
    iii) Use Async/Await  
    Even better readability, and it looks synchronous
    
    ```javascript
    async function processAll() {
        try {
            const result1 = await doSomething();
            const result2 = await doSomethingElse(result1);
            const result3 = await doAnotherThing(result2);
            const result4 = await doFinalThing(result3);
        } catch (error) {
            console.error(error);
        }
    }
    
    processAll();
    ```
    
2. **<mark>What are JavaScript Promises? Explain their states.</mark>**  
    A **Promise** in JavaScript is an object that represents the eventual **completion** (or **failure**) of an asynchronous operation and its resulting **value**.  
    It’s like a placeholder for a value that will be known in the future — perfect for dealing with asynchronous tasks like API calls, file loading, timers, etc.
    
    ```javascript
    // Basic Syntax
    const promise = new Promise((resolve, reject) => {
        // async operation
        if (success) {
            resolve("Data received");
        } else {
            reject("Something went wrong");
        }
    });
    ```
    
    A Promise has **three states**  
    **i) Pending**
    
    Initial state.
    
    The operation is still ongoing (neither fulfilled nor rejected).
    
    Example: waiting for an API response.
    
    **ii) Fulfilled**
    
    The operation completed **successfully**.
    
    `resolve(value)` is called.
    
    The promise returns a result.
    
    Handled with `.then()` or `await`.
    
    **iii) Rejected**
    
    The operation **failed**.
    
    `reject(error)` is called.
    
    An error is returned.
    
    Handled with `.catch()` or a `try/catch` block in `async/await`.
    
    ```javascript
    let fetchData = new Promise((resolve, reject) => {
        let success = true;
    
        setTimeout(() => {
            if (success) {
                resolve("Data loaded");
            } else {
                reject("Failed to load data");
            }
        }, 2000);
    });
    
    fetchData
        .then(data => console.log("Success:", data))
        .catch(error => console.error("Error:", error));
    ```
    
    Once a promise is fulfilled or rejected, it is **settled** — and can't change its state anymore.
    
3. **<mark>How to handle fetch Data API using Promises?</mark>**  
    The `fetch()` function is a modern way to make HTTP requests. It returns a **Promise** that resolves to the `Response` object representing the result of the request.
    
    ```javascript
    // GET Method using Promise
    fetch('https://jsonplaceholder.typicode.com/posts/1')
        .then(response => {
            if (!response.ok) {
                throw new Error('Network response was not ok');
            }
            return response.json(); // parse JSON from response
        })
        .then(data => {
            console.log("Fetched data:", data);
        })
        .catch(error => {
            console.error("Fetch error:", error);
        });
    ```
    
    `fetch(url)`: Initiates a request and returns a Promise.
    
    `.then(response => ...)`: Handles the response object.
    
    `response.json()`: Converts the response body to JSON (also returns a Promise).
    
    `.then(data => ...)`: Accesses the actual data.
    
    `.catch(error => ...)`: Catches any network or parsing errors.  
    **Always check response.ok -**
    
    This checks if the HTTP status is in the `200–299` range (success). Otherwise, you might think it worked even if it returned a 404 or 500.
    
    Handling a **POST API** request using **Promises** with `fetch()` is very straightforward — you just need to: Set the `method` to `"POST"`. Include `headers` (especially `"Content-Type": "application/json"`). Pass the request body as a JSON string using `JSON.stringify()`.
    
    ```javascript
    // POST Method using Promise
    const postData = {
        title: 'Hello World',
        body: 'This is a post request example',
        userId: 1
    };
    
    fetch('https://jsonplaceholder.typicode.com/posts', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json' // Tells server you're sending JSON
        },
        body: JSON.stringify(postData) // Convert JS object to JSON string
    })
    .then(response => {
        if (!response.ok) {
            throw new Error('Network response was not ok');
        }
        return response.json(); // Parse JSON response
    })
    .then(data => {
        console.log("POST successful, response data:", data);
    })
    .catch(error => {
        console.error("POST failed:", error.message);
    });
    ```
    
4. **<mark>How to handle fetch Data API using Async/Await?</mark>**  
    Using `async/await` with the `fetch()` API is a clean and modern way to handle asynchronous data fetching in JavaScript.
    
    ```javascript
    // GET Method using Async/Await
    async function fetchData() {
        try {
            const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
            
            if (!response.ok) {
                throw new Error(`HTTP error! Status: ${response.status}`);
            }
    
            const data = await response.json();
            console.log("Fetched data:", data);
        } catch (error) {
            console.error("Fetch error:", error.message);
        }
    }
    
    fetchData();
    ```
    
    `async function`: Declares an asynchronous function.
    
    `await fetch(...)`: Waits for the `fetch()` Promise to resolve.
    
    `response.ok`: Checks if the request was successful.
    
    `await response.json()`: Waits for the response to be parsed to JSON.
    
    `try...catch`: Catches and handles any errors (like network issues or bad responses).  
    Let's handle a **POST API** using `async/await` — it’s clean, modern, and easy to read.
    
    ```javascript
    // POST Method using Async/Await
    async function postData() {
        const payload = {
            title: 'Hello World',
            body: 'This is a post request using async/await',
            userId: 1
        };
    
        try {
            const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(payload)
            });
    
            if (!response.ok) {
                throw new Error(`HTTP error! Status: ${response.status}`);
            }
    
            const data = await response.json();
            console.log("POST successful:", data);
        } catch (error) {
            console.error("POST request failed:", error.message);
        }
    }
    
    postData();
    ```
    
    `async function`: Declares an asynchronous function.
    
    `await fetch(...)`: Waits for the HTTP request to complete.
    
    `method: 'POST'`: Specifies you're sending data.
    
    `headers`: Tells the server the content type is JSON.
    
    `body: JSON.stringify(...)`: Converts the JavaScript object to a JSON string.
    
    `await response.json()`: Waits for and parses the JSON response.
    
    `try...catch`: Catches and handles errors like network failures or bad responses.
    
5. **<mark>What is the difference between .then() and .catch() in promises?</mark>**  
    `.then()` and `.catch()` are essential tools for handling **Promises** in JavaScript — they help you deal with **success** and **error** outcomes of asynchronous operations.  
    **i)** `.then()` **– Handles Success**  
    `.then()` is used when a **Promise is fulfilled (resolved)** successfully. It allows you to **access the result** of the Promise.
    
    ```javascript
    fetch('https://api.example.com/data')
        .then(response => response.json()) // handles the fulfilled state
        .then(data => console.log("Data:", data)); // chained for further processing
    ```
    
    **ii)** `.catch()` **– Handles Errors**
    
    `.catch()` is used when a Promise is rejected or if an error is thrown anywhere in the chain before it.
    
    ```javascript
    fetch('https://api.example.com/data')
        .then(response => response.json())
        .then(data => console.log("Data:", data))
        .catch(error => console.error("Error:", error)); // handles errors
    ```
    
    **Difference between Try and Catch in Promises -**
    
    i) Purpose  
    try - Handles **success**  
    catch - Handles **errors/failures**  
    ii) Receives  
    try - The resolved value  
    catch - The error object  
    iii) Return Type  
    try - Returns a new Promise  
    catch - Also returns a new Promise  
    iv) Usage  
    try - For continuing flow  
    catch - For catching and responding to errors
    
6. **<mark>What are the differences between Promise.all, Promise.race, Promise.any, and Promise.allSettled?</mark>**  
    These four Promise utility methods are powerful for handling **multiple asynchronous operations** in JavaScript, and each behaves differently based on how the Promises resolve or reject.  
    **i) Promise.all()**  
    It waits for **all Promises to resolve**. If anyone fails, it rejects immediately.
    
    ```javascript
    Promise.all([promise1, promise2, promise3])
        .then(results => console.log("All resolved:", results))
        .catch(error => console.error("One failed:", error));
    ```
    
    It returns array of all resolved values after all the promises gets resolved.
    
    **ii) Promise.race()**  
    It returns the **first settled** promise (either resolved or rejected).
    
    ```javascript
    Promise.race([promise1, promise2, promise3])
        .then(result => console.log("First settled (resolved):", result))
        .catch(error => console.error("First settled (rejected):", error));
    ```
    
    iii) Promise.any()  
    It returns the **first resolved** promise. Ignores rejections unless **all** fail.
    
    ```javascript
    Promise.any([promise1, promise2, promise3])
        .then(result => console.log("First resolved:", result))
        .catch(error => console.error("All failed:", error));
    ```
    
    iv) Promise.allSettled()  
    It waits for **all Promises to settle**, regardless of outcome.
    
    ```javascript
    Promise.allSettled([promise1, promise2, promise3])
        .then(results => {
            results.forEach(result => {
                if (result.status === "fulfilled") {
                    console.log("Resolved:", result.value);
                } else {
                    console.log("Rejected:", result.reason);
                }
            });
        });
    ```
    
    Use Case Summary -
    
    i) Use `Promise.all()` when **all must succeed**
    
    ii) Use `Promise.race()` when **only the fastest matters**
    
    iii) Use `Promise.any()` when **any one success is enough**
    
    iv) Use `Promise.allSettled()` when you **need all results regardless of success/failure**
    
7. **<mark>How do you cancel an ongoing promise?</mark>**  
    **i) Use AbortController (for fetch requests)**
    
    ```javascript
    const controller = new AbortController();
    const signal = controller.signal;
    
    fetch('https://jsonplaceholder.typicode.com/posts', { signal })
        .then(response => response.json())
        .then(data => console.log(data))
        .catch(error => {
            if (error.name === 'AbortError') {
                console.log('Fetch aborted');
            } else {
                console.error('Fetch error:', error);
            }
        });
    
    // Cancel the fetch after 2 seconds
    setTimeout(() => controller.abort(), 2000);
    ```
    
    **ii) Custom Cancelable Promise Wrapper**  
    You can wrap a Promise to make it **cancellable manually**, though this only works for operations you control (like timeouts or intervals).
    
    ```javascript
    function cancellablePromise(executor) {
        let cancel;
        const wrapped = new Promise((resolve, reject) => {
            cancel = () => reject(new Error("Cancelled"));
            executor(resolve, reject);
        });
        wrapped.cancel = cancel;
        return wrapped;
    }
    
    const promise = cancellablePromise((resolve, reject) => {
        setTimeout(() => resolve("Done!"), 3000);
    });
    
    promise.then(console.log).catch(console.error);
    
    // Cancel before it completes
    setTimeout(() => promise.cancel(), 1000);
    ```
    
    **iii) Reactive Libraries / Promises with Cancel Support**  
    If you're using libraries like,
    
    **RxJS** (with Observables)
    
    **Axios** (with cancel tokens — now deprecated, but had similar logic)
    
    **Bluebird** (with cancellable Promises)
    
8. **<mark>How do you handle promises sequentially?</mark>**  
    Handling **Promises sequentially** means making sure **each Promise runs only after the previous one has finished** — super useful for things like - Making API calls in order, Performing async tasks one-by-one, Respecting rate limits.  
    **i) Using** `async/await` **(Cleanest Way)**
    
    ```javascript
    async function runSequentially() {
        const result1 = await fetch('https://jsonplaceholder.typicode.com/posts/1');
        const data1 = await result1.json();
        console.log("Post 1:", data1);
    
        const result2 = await fetch('https://jsonplaceholder.typicode.com/posts/2');
        const data2 = await result2.json();
        console.log("Post 2:", data2);
    
        const result3 = await fetch('https://jsonplaceholder.typicode.com/posts/3');
        const data3 = await result3.json();
        console.log("Post 3:", data3);
    }
    
    runSequentially();
    ```
    
    **ii) Sequentially Loop Through an Array**
    
    ```javascript
    const urls = [
        'https://jsonplaceholder.typicode.com/posts/1',
        'https://jsonplaceholder.typicode.com/posts/2',
        'https://jsonplaceholder.typicode.com/posts/3'
    ];
    
    async function fetchSequentially(urls) {
        for (let url of urls) {
            const res = await fetch(url);
            const data = await res.json();
            console.log(data);
        }
    }
    
    fetchSequentially(urls);
    ```
    
    **iii) Using** `.then()` **Chaining**
    
    ```javascript
    fetch('https://jsonplaceholder.typicode.com/posts/1')
        .then(res => res.json())
        .then(data1 => {
            console.log("Post 1:", data1);
            return fetch('https://jsonplaceholder.typicode.com/posts/2');
        })
        .then(res => res.json())
        .then(data2 => {
            console.log("Post 2:", data2);
            return fetch('https://jsonplaceholder.typicode.com/posts/3');
        })
        .then(res => res.json())
        .then(data3 => {
            console.log("Post 3:", data3);
        })
        .catch(err => console.error("Error:", err));
    ```
    
9. **<mark>What is async/await, and What are the benefits of using async/await over promises?</mark>**  
    `async/await` is **syntactic sugar** over Promises introduced in **ES2017 (ES8)**. It makes asynchronous code look and behave more like synchronous code — making it **easier to read, write, and debug**.
    
    ```javascript
    async function fetchData() {
        try {
            const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
            const data = await response.json();
            console.log("Data:", data);
        } catch (error) {
            console.error("Error:", error);
        }
    }
    ```
    
    `async` keyword: declares a function that returns a Promise.
    
    `await` keyword: pauses execution until the Promise settles (resolves or rejects).  
    **Benefits of Using** `async/await` **Over Promises -**  
    i) Clean, readable code  
    Feels like synchronous code - easier to understand & maintain  
    ii) Easier error handling  
    Use `try/catch` instead of multiple `.catch()` blocks  
    iii) Avoids callback hell  
    Reduces deep `.then().then().then()` nesting  
    iv) Great for sequencing  
    Easy to run async tasks **one after another** (sequentially)  
    v) Debug-friendly  
    Debuggers and stack traces are easier to follow  
    **When Not to Use** `await` **\-**  
    i) When you need **parallel execution** (`Promise.all()` is better)
    
    ii) When handling **many short tasks** where batching is faster
    
10. **<mark>How do you handle errors with async/await?</mark>**  
    Error handling with `async/await` is one of its biggest strengths — it makes dealing with asynchronous failures **clean and readable** using `try...catch`.
    
    ```javascript
    async function fetchData() {
        try {
            const response = await fetch('https://jsonplaceholder.typicode.com/invalid-url');
            
            if (!response.ok) {
                throw new Error(`HTTP error! Status: ${response.status}`);
            }
    
            const data = await response.json();
            console.log("Data:", data);
        } catch (error) {
            console.error("Caught error:", error.message);
        }
    }
    
    fetchData();
    ```
    
    `try` block - Place your `await` calls here.
    
    `catch` block - Catches: Network errors, JSON parsing errors, Manually thrown errors (like `throw new Error()`)
    
    Always Check `response.ok`
    
    Even if `fetch()` succeeds (as in: doesn't throw), it can still return a 404 or 500. So always check
    
    ```javascript
    if (!response.ok) {
        throw new Error("Request failed with status: " + response.status);
    }
    ```
    
11. **<mark>What are generators in Javascript, and how do they work?</mark>**
    
    **Generators** are special functions in JavaScript that can **pause** and **resume** their execution.
    
    They are declared using the `function*` syntax and use the `yield` keyword to **pause and return a value** at each step.
    
    ```javascript
    function* myGenerator() {
        yield "First";
        yield "Second";
        yield "Third";
    }
    
    const gen = myGenerator();
    
    console.log(gen.next()); // { value: "First", done: false }
    console.log(gen.next()); // { value: "Second", done: false }
    console.log(gen.next()); // { value: "Third", done: false }
    console.log(gen.next()); // { value: undefined, done: true }
    // yield → Think of it like a return, but with the ability to come back later.
    ```
    
    i) When you call a generator function, it **doesn’t run immediately**.
    
    ii) It returns a **generator object** (an iterator).
    
    iii) Each call to `.next()` runs the generator until it hits the next `yield`.
    
    function\* - Declares a generator function  
    yield - Pauses the function and returns a value  
    .next() - Resumes the function from where it left off  
    done - A flag returned by `.next()` indicating if the generator has finished
    
    **Generators Are Perfect for -**  
    i) Lazy loading / streaming values - Generate values on demand  
    ii) Custom iterators - Build powerful `for...of` loops  
    iii) Async control flows (via `yield`) - Advanced use with async tools like redux-saga  
    iv) Pausing expensive computations - Break down long tasks into smaller pieces
    
12. **<mark>What are mixins in JavaScript?</mark>**  
    **Mixins** are a pattern used to **add reusable behaviors (methods/properties) to classes or objects**, **without using inheritance**.
    
    Think of them like **“plug-in features”** you can mix into multiple classes.  
    JavaScript doesn't support **multiple inheritance**, but you often want a class to **share behaviors from multiple sources**.
    
    Mixins solve that by letting you **compose** functionality.
    
    ```javascript
    let canEat = {
        eat() {
            console.log(`${this.name} is eating`);
        }
    };
    
    let canWalk = {
        walk() {
            console.log(`${this.name} is walking`);
        }
    };
    
    class Person {
        constructor(name) {
            this.name = name;
        }
    }
    
    // Mix behaviors into the class prototype
    Object.assign(Person.prototype, canEat, canWalk);
    
    const user = new Person("Alice");
    user.eat();  // Alice is eating
    user.walk(); // Alice is walking
    ```
    
13. **<mark>What is currying in JavaScript? Provide an example.</mark>**  
    **Currying** is a technique where a function, instead of taking all arguments at once, takes them **one at a time** — returning a new function each time until all arguments are received.  
    In short: **Convert a function** `f(a, b)` into `f(a)(b)`
    
    ```javascript
    // Normal function
    function add(a, b) {
        return a + b;
    }
    
    // Curried version
    function curriedAdd(a) {
        return function(b) {
            return a + b;
        };
    }
    
    console.log(add(2, 3));         // 5
    console.log(curriedAdd(2)(3));  // 5
    ```
    
    **Why Use Currying? -**
    
    i) Reusability - Create specialized functions by pre-filling args  
    ii) Functional composition - Easier to chain and combine small functions  
    iii) Cleaner syntax - Especially in frameworks like React or Redux
    
14. **<mark>Explain the debouncing, and when would you use it?</mark>**  
    **Debouncing** is a technique used to **limit how often a function is executed** — especially when it's triggered by **high-frequency events** like Typing, Scrolling, Resizing, Button clicks.  
    It ensures that the function only runs **after a specified delay**, and **only if** the event **stops happening during that delay**.  
    Imagine you're typing in a search box — instead of firing a search API request on **every keystroke**, you debounce it to wait **300ms after you stop typing**.
    
    ```javascript
    function debounce(fn, delay) {
        let timeout;
        return function(...args) {
            clearTimeout(timeout);
            timeout = setTimeout(() => {
                fn.apply(this, args);
            }, delay);
        };
    }
    
    // Example usage
    function searchHandler(query) {
        console.log("Searching for:", query);
    }
    
    const debouncedSearch = debounce(searchHandler, 500);
    
    // Simulate typing
    debouncedSearch("H");
    debouncedSearch("He");
    debouncedSearch("Hel");
    debouncedSearch("Hell");
    debouncedSearch("Hello"); // Only this will trigger after 500ms
    ```
    
    **When to Use Debouncing -**  
    i) Search inputs - Reduce unnecessary API calls while typing  
    ii) Window resize events - Avoid performance hits from rapid firing  
    iii) Scroll-based animations/lazy load - Smooth rendering, fewer updates  
    iv) Form validation while typing - Don’t validate on every keystroke
    
15. **<mark>Explain the throttling, and when would you use it?</mark>**  
    **Throttling** is a technique that ensures a function is **called at most once every X milliseconds**, no matter how many times the event is triggered.
    
    So even if the event **fires constantly**, the throttled function only runs **at fixed intervals**.  
    Imagine someone trying to call you 100 times in a minute, but your phone only lets one call through every 5 seconds — that’s **throttling**.
    
    ```javascript
    function throttle(fn, delay) {
        let lastCall = 0;
        return function(...args) {
            const now = new Date().getTime();
            if (now - lastCall >= delay) {
                lastCall = now;
                fn.apply(this, args);
            }
        };
    }
    
    // Usage
    function logScrollPosition() {
        console.log("Scroll Y:", window.scrollY);
    }
    
    window.addEventListener('scroll', throttle(logScrollPosition, 300));
    // In this case, even if the user scrolls rapidly, 
    // the function logs scroll position at most every 300ms.
    ```
    
    **When to Use Throttling -**  
    i) Scroll events - Avoid flooding the browser with too many calls  
    ii) Window resize - Optimize layout recalculations  
    iii) Button mash prevention - Prevent spamming API or animations  
    iv) Continuous input tracking - Log or track at steady intervals
    
16. **<mark>What is difference between debouncing and throttling?</mark>**  
    **i) Definition**  
    Debouncing - Delay execution until after a certain time has **passed** since the last event.  
    Throttling - Limits execution to **once every specified time interval**, no matter how many events happen.  
    i**i) When function runs**  
    Debouncing - After **no events** occur for the delay period.  
    Throttling - **At regular intervals** during continuous event triggering.  
    **iii) Purpose**  
    Debouncing - Ensure the function runs **only once** after rapid events stop.  
    Throttling - Ensure the function runs **at steady intervals** during rapid events.  
    **iv) Common Use Cases**  
    Debouncing - Auto-save, search input filtering, window resizing (after stop typing/resizing).  
    Throttling - Scrolling events, mouse movements, window resizing (smooth updates while happening).  
    **v) Example Behavior**  
    Debouncing - Wait until user stops typing to send an API request.  
    Throttling - Track scrolling position every 300ms, even during heavy scroll.  
    **vi) If you want**  
    Debouncing - "Do it once after the user stops doing it"  
    Throttling - "Do it regularly while the user is doing it"
    
17. **<mark>Explain the concept of Event propagation?</mark>**  
    **Event propagation** is the way events **travel through the DOM tree**.  
    It happens in **three phases,**
    
    i) **Capturing Phase (Trickling Down)**
    
    The event starts at the **window** and travels **down** to the target element.  
    ii) **Target Phase**
    
    The event reaches the **target element** (where the event actually occurred).  
    iii) **Bubbling Phase (Bubbling Up)**
    
    The event then bubbles **up** from the target to the **root** (window).  
    Imagine clicking a button inside a div inside the body:
    
    ```css
    window → document → body → div → button (target)
                                    ↑
                              bubbles back up
    ```
    
    ```xml
    <div id="outer">
        <button id="inner">Click me</button>
    </div>
    ```
    
    ```javascript
    document.getElementById("outer").addEventListener("click", () => {
        console.log("Outer clicked");
    }, true); // Capturing phase
    
    document.getElementById("inner").addEventListener("click", () => {
        console.log("Inner clicked");
    });
    ```
    
    **Controlling Propagation -**  
    i) event.stopPropagation()  
    Stops further propagation (no bubbling/capturing)  
    ii) event.stopImmediatePropagation()  
    Also blocks other listeners on the same element  
    iii) event.preventDefault()  
    Prevents default browser behavior (like form submission)
    
18. **<mark>How do you prevent the default action of an event?</mark>**  
    you can **prevent the default action** of an event (like a link navigating, a form submitting, etc.) by calling event.preventDefault();
    
    ```javascript
    <form id="myForm">
      <input type="text" name="name" />
      <button type="submit">Submit</button>
    </form>
    
    <script>
    document.getElementById("myForm").addEventListener("submit", function(event) {
        event.preventDefault(); // Stops the form from submitting
        console.log("Form submission prevented!");
    });
    </script>
    ```
    
19. **<mark>What is the difference between event delegation and event bubbling?</mark>**  
    **Event bubbling** and **event delegation** are closely related concepts, but they serve different purposes.  
    **i) Event Bubbling**  
    A mechanism where an event starts from the **target element** and **bubbles up** through its ancestors.
    
    ```xml
    <div id="parent">
      <button id="child">Click Me</button>
    </div>
    ```
    
    ```javascript
    document.getElementById("parent").addEventListener("click", () => {
      console.log("Parent clicked");
    });
    
    document.getElementById("child").addEventListener("click", () => {
      console.log("Child clicked");
    });
    ```
    
    Bubbling happens automatically unless stopped with `event.stopPropagation()`.
    
    **ii) Event Delegation**  
    It is a pattern where you **attach one event listener to a parent**, and use `event.target` to detect which child element was clicked.  
    It is useful for handling **events on dynamically added elements** or **reducing multiple listeners**.
    
    ```javascript
    document.getElementById("parent").addEventListener("click", (event) => {
      if (event.target.tagName === "BUTTON") {
        console.log("Button clicked:", event.target.textContent);
      }
    });
    ```
    
20. **<mark>How does JavaScript handle memory leaks?</mark>**  
    JavaScript has **automatic memory management** — meaning it uses a **Garbage Collector** to free up memory that is **no longer needed**.  
    If something is no longer **reachable** (nothing references it anymore), the garbage collector removes it from memory. But **Memory leaks can still happen** when you **accidentally keep references** to things you no longer need!  
    Chrome DevTools, Firefox DevTools, etc. can **detect and profile memory leaks**.  
    Tools like **Heap Snapshots** help you track down objects still stuck in memory.  
    **How to Minimize/Prevent Memory Leaks -**  
    i) Use `let`, `const` instead of globals - Limits variable scope  
    ii) Clean up timers and listeners - `clearInterval()`, `removeEventListener()` when no longer needed  
    iii) Nullify references manually - `element = null;` when done  
    iv) Use WeakMap/WeakSet for caching - They don't prevent garbage collection
    
21. **<mark>How do you use Web Workers in JavaScript for multi-threading?</mark>**  
    **Web Workers** allow JavaScript to **run in the background** on a **separate thread**.
    
    This means they can **do heavy computations** without **blocking** the main UI thread — keeping your app smooth and responsive!  
    **JavaScript is single-threaded by default**, but Web Workers give you a way to simulate **multi-threading**.  
    **How to Use Web Workers -**  
    i) Create a **separate JS file** for the worker code.
    
    ii) Instantiate a `Worker` in your main script.
    
    iii) Use `postMessage()` to send data to the worker.
    
    iv) The worker responds back with `postMessage()` too.
    
    ```javascript
    // worker.js
    
    // Listen for messages from main thread
    self.addEventListener('message', function(event) {
      const result = event.data * 2;  // Simple computation
      self.postMessage(result);       // Send result back
    });
    ```
    
    ```javascript
    // main.js
    
    // Create a new worker
    const worker = new Worker('worker.js');
    
    // Listen for messages from the worker
    worker.addEventListener('message', function(event) {
      console.log("Result from worker:", event.data);
    });
    
    // Send data to the worker
    worker.postMessage(10);
    ```
    
    **Why Use Web Workers? -**  
    i) Heavy calculations (e.g., image processing, video encoding).
    
    ii) Real-time data crunching (e.g., financial charts, game physics).
    
    iii) Background syncing, offline tasks.
    
22. **<mark>What is memoization in Javascript, and how is it used?</mark>**  
    **Memoization** is a **caching technique**, It stores the **results of expensive function calls** and returns the **cached result** when the same inputs occur again.
    
    This improves **performance**, especially for **heavy** or **repetitive** computations.
    
    ```javascript
    // Simple Example Without Memoization
    function slowFunction(num) {
      console.log("Calculating...");
      return num * num;
    }
    
    console.log(slowFunction(5)); // "Calculating..." then 25
    console.log(slowFunction(5)); // "Calculating..." again!
    // The function recomputes even if the input is the same.
    ```
    
    ```javascript
    // Memoized Version
    function memoize(fn) {
      const cache = {};
      return function(...args) {
        const key = JSON.stringify(args); // create a unique key based on arguments
        if (cache[key]) {
          return cache[key];
        } else {
          const result = fn.apply(this, args);
          cache[key] = result;
          return result;
        }
      };
    }
    
    // Usage
    const optimizedFunction = memoize(slowFunction);
    
    console.log(optimizedFunction(5)); // "Calculating..." then 25
    console.log(optimizedFunction(5)); // Instant 25 from cache (no "Calculating...")
    // Now the result is cached, and the second call is instant!
    ```
    
23. **<mark>What are pure functions in JavaScript?</mark>**  
    A **pure function** is a function that:
    
    **Given the same input**, always **returns the same output**.
    
    **Does not cause any side effects** (like modifying a global variable, DOM manipulation, writing to a file, etc.).
    
    ```javascript
    function add(a, b) {
      return a + b;
    }
    
    console.log(add(2, 3)); // Always 5
    console.log(add(2, 3)); // Always 5, no side effects
    ```
    
    **Why Pure Functions are Important -**
    
    **Easier to debug** and test.
    
    **Predictable** behavior (no surprises!).
    
    Great for **functional programming** (React, Redux, etc.).
    
    Makes code **more reusable** and **maintainable**.
    
24. **<mark>What are some best practices for securing JavaScript applications?</mark>**  
    **i) Avoid Exposing Sensitive Information -**
    
    Never store API keys, passwords, or secrets in your frontend code.
    
    Use environment variables on the server side and APIs to handle sensitive tasks.  
    **ii) Use HTTPS -**
    
    Always serve your website over HTTPS.
    
    Protects against **man-in-the-middle attacks** and data sniffing.  
    **iii) Input Validation and Sanitization -**
    
    Validate and sanitize **all user input** (both on **frontend** and **backend**).
    
    Prevents attacks like **Cross-Site Scripting (XSS)** and **SQL Injection**.  
    **iv) Prevent Cross-Site Scripting (XSS)**
    
    Never directly inject user input into the DOM.
    
    Escape or sanitize outputs.
    
    Use security libraries like: DOMPurify (to sanitize HTML safely)  
    **v) Use Content Security Policy (CSP)**
    
    Set up a **CSP header** to restrict what resources the browser can load.
    
    Helps prevent XSS attacks by blocking unauthorized scripts.
    
    ```xml
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'">
    ```
    
    **vi) Be Careful with** `eval()` **and** `new Function()`
    
    **Avoid** using `eval()` — it can execute arbitrary code, which is very dangerous.
    
    Same caution applies to `setTimeout(string)` and `setInterval(string)`.  
    **vii) Keep Dependencies Updated**
    
    Vulnerabilities often come through outdated libraries.
    
    Regularly update your npm/yarn packages.
    
    Use tools like:`npm audit, Snyk, Dependabot`  
    **viii)** **Use Proper Authentication and Authorization**
    
    Use libraries like **OAuth**, **JWT** for authentication.
    
    Implement **role-based access control** (RBAC) on the server.
    
    Never trust client-side authentication alone!
    
    **ix) Protect Against Cross-Site Request Forgery (CSRF)**
    
    For APIs, use anti-CSRF tokens.
    
    Set appropriate CORS (Cross-Origin Resource Sharing) policies.
    
    **x) Limit Error Messages**
    
    Avoid showing **stack traces** or **internal errors** to users.
    
    Log detailed errors **server-side**, but show **generic** errors to users.
