Javascript for Mid Level
Javascript for Mid Level
JAVASCRIPT 01/04/2025
AND ANSWERS
FOR MID-LEVEL DEVELOPERS
This document contains a curated selection of JavaScript interview
questions specifically designed for mid-level developers. Each answer
WHAT'S INSIDE
provides in-depth explanations to help you demonstrate practical
experience and a strong understanding of core concepts.
1. What is the difference between shallow copy and deep copy in JavaScript?
A shallow copy creates a new object, but only copies the references to nested objects — not the
actual nested objects themselves. A deep copy, on the other hand, recursively copies all nested
levels, creating a completely independent clone.
In practice, I use shallow copy (like with the spread operator) when I’m working with flat objects or
arrays. But for deeply nested structures, I use techniques like structuredClone() (in modern
browsers) or libraries like Lodash’s cloneDeep() to avoid shared references that can lead to
unintended mutations.
2. What are JavaScript promises and how do they differ from callbacks?
Using promises makes async code easier to reason about, especially when combined with
async/await, which offers a more synchronous-looking syntax without blocking the event loop.
Object.freeze() makes an object completely immutable — you can’t add, remove, or modify any
properties.
Object.seal() allows modification of existing properties but prevents adding or deleting keys.
I use freeze when I want a truly read-only structure, such as configuration constants, and seal
when I want to allow changes but keep the object’s shape fixed.
The event loop is what allows JavaScript — a single-threaded language — to handle asynchronous
operations like timers, HTTP requests, or UI events without blocking the main thread.
The loop monitors the call stack and task queue. If the stack is empty, it takes the first task from
the queue and executes it. Understanding this mechanism helps avoid issues like race conditions,
unresponsive interfaces, or performance bottlenecks.
As a developer, I consider how code is queued (microtasks vs. macrotasks) and optimize long-
running tasks to avoid blocking the UI thread.
A memory leak occurs when allocated memory is no longer needed but isn’t released due to
lingering references. Common causes include forgotten timers, global variables, and closures
retaining references unintentionally.
To prevent memory leaks, I clean up event listeners, clear intervals and timeouts, and avoid
unnecessary global variables. I also use browser dev tools to inspect memory usage and detect
detached DOM elements.
6. How do == and === differ, and when would you use each?
== compares two values with type coercion, while === checks both type and value strictly.
In professional code, I always prefer === because it avoids unexpected behavior due to implicit
type conversion. It improves readability and prevents subtle bugs, especially when working with
dynamic input or third-party data.
I reserve == only when coercion is explicitly intended and well-understood — which is rare in
modern codebases.
A closure is created when a function retains access to its outer scope, even after the outer function
has returned. This allows the inner function to "remember" the variables from the context it was
defined in.
Closures are useful for encapsulation, private state, and function factories. I often use them in
functional programming patterns, event handlers, and module patterns where maintaining internal
state without exposing it globally is essential.
Arrow functions don’t have their own this. Instead, they lexically bind this from the surrounding
context. Regular functions, on the other hand, define their own this depending on how they’re
called.
I use arrow functions when I need to preserve the parent this, especially inside callbacks or class
methods. But for object methods or constructors, I prefer regular functions where a dynamic this is
expected.
9. What are the main differences between Map and Object in JavaScript?
While both can store key-value pairs, Map offers several advantages over plain objects:
I use Map when I need consistent order, non-string keys, or better performance for frequent
additions/removals. I use Object for static structures or when integrating with JSON data.
Synchronous code blocks the execution thread — each line must finish before the next one runs.
Asynchronous code allows non-blocking behavior, where operations like API calls can run in the
background.
undefined means a variable was declared but not assigned a value. It's the default state of
uninitialized variables.
null is an intentional assignment to indicate "no value". I use it when I explicitly want to clear a
value or show the absence of a reference.
void 0 is an expression that always evaluates to undefined. It’s mostly used in older patterns
to safely produce undefined in environments where it might have been reassigned.
Understanding the nuances between these helps me write clear conditions and avoid confusing
falsy checks.
JavaScript uses prototypal inheritance, where objects inherit directly from other objects via a
hidden internal property called [[Prototype]]. This means if a property or method is not found on the
object itself, JavaScript looks up the prototype chain.
I leverage this model when creating shared methods via constructors or class syntax. It promotes
memory efficiency and allows me to structure reusable behaviors without duplication.
13. What are the differences between set and map in JavaScript?
Set is a collection of unique values, whereas Map is a collection of key-value pairs. In Set, values
cannot repeat, and order is preserved.
Map allows any type of key, maintains insertion order, and is better optimized for frequent
additions and deletions.
I use Set for de-duplicating arrays or checking memberships, and Map when keys aren’t just strings
and performance matters in lookups.
A thunk is a function that wraps an expression to delay its evaluation. In JavaScript, a thunk often
refers to a function that returns another function, typically used to defer computation or control
execution.
In Redux middleware, thunks are used to delay dispatching actions until certain conditions are met
— for example, after an async API call. Thunks are powerful tools when implementing lazy
evaluation, currying, or async control flow.
Debouncing delays the execution of a function until after a specified time has passed since the
last event. Useful for input fields or auto-saves.
Throttling ensures the function is executed at most once in a specified interval. Great for scroll
tracking or resizing.
I choose between them depending on whether I care about the last event (debounce) or regular
intervals (throttle).
Optional chaining allows safe access to deeply nested properties without manually checking for
null or undefined.
It improves code readability and reduces runtime errors when working with complex or uncertain
data, especially from external APIs.
The Temporal Dead Zone is the period between the start of a block and the point where a let or
const variable is declared. Accessing the variable during this phase throws a ReferenceError.
This is why I always declare variables at the top of a block and avoid using let/const before their
declaration. It’s a key difference from var, which is hoisted and initialized as undefined.
Garbage collection is automatic in JavaScript. It removes objects from memory when there are no
more references to them.
The most common algorithm is mark-and-sweep, where the engine marks reachable objects and
sweeps away the unreachable ones.
While I don’t manage memory directly, I avoid memory leaks by cleaning up timers, DOM references,
event listeners, and ensuring proper lifecycle handling in components.
These components allow asynchronous behavior without blocking, letting JavaScript respond to
user input, fetch data, or handle timers concurrently without multi-threading.
Tagged template literals allow me to process a template literal with a custom function. The tag
function receives the string parts and interpolated values separately, enabling advanced
formatting, escaping, or even localization logic.
They’re useful when working with styling libraries (like styled-components), i18n, or sanitizing
user-generated input.
21. What are generators in JavaScript and how are they used?
Generators are special functions that can be paused and resumed during execution. They are
defined using the function* syntax and use the yield keyword to pause the function and return a
value.
The main benefit of generators is that they allow for lazy evaluation and custom iteration. Unlike
regular functions that run to completion, generators give me more control over the execution flow. I
can use them to model sequences, streams of data, or even implement async-like behavior before
async/await was standardized.
They’re useful in use cases like paginated data loading, building custom iterators, or managing
complex control flows in a readable way.
Under the hood, async/await is syntactic sugar built on top of Promises. When I declare a function
with the async keyword, it always returns a Promise, even if it looks like a synchronous function.
When I use await, it pauses the execution of the function until the awaited Promise is resolved or
rejected. Internally, the JavaScript engine transforms async/await code into a promise chain with
.then() and .catch(), managing the control flow for me.
This approach makes asynchronous code more readable and easier to debug. However, I must
always remember to wrap await calls in try/catch blocks to handle errors gracefully.
23. What are WeakMap and WeakSet, and when would you use them?
WeakMap and WeakSet are special types of collections in JavaScript that hold weak references to
their keys. This means the keys (which must be objects) can be garbage collected if there are no
other references to them elsewhere in the code.
They don’t prevent memory from being freed and are useful for storing metadata or private data
associated with objects, without interfering with their lifecycle.
For example, I might use a WeakMap to store internal state in a library without exposing it to
consumers, ensuring that once the object is no longer in use, its metadata is also collected by the
garbage collector. They’re especially valuable in situations where I want to avoid memory leaks in
long-running applications.
24. What is the difference between microtasks and macrotasks in the event loop?
In the JavaScript event loop model, tasks are divided into macrotasks (e.g. setTimeout, setInterval,
I/O) and microtasks (e.g. Promises, queueMicrotask, MutationObserver).
After each macrotask, the engine processes all the microtasks in the queue before moving on to the
next macrotask. This is why Promise.then() callbacks often run before setTimeout callbacks, even if
the timeout is set to zero.
Understanding this mechanism is crucial for debugging asynchronous code, especially when order
of execution matters. I use this knowledge to optimize performance-sensitive code and avoid race
conditions in UI updates or data flows.
25. What are modules in JavaScript and how do they work in ES6?
Modules in ES6 allow me to split code into separate files and import/export functionality as
needed. This promotes reusability, maintainability, and a cleaner global scope.
Each module has its own lexical scope. I can define functions, variables, and classes that are
scoped to the module unless I explicitly export them. Other modules can then import these exports
selectively.
Modules are loaded asynchronously by the browser (with type="module"), support static analysis,
and work well with tools like bundlers and tree-shaking optimizers. I use modules to build scalable
applications with a clear structure, separating concerns and minimizing dependencies.
26. How does destructuring work and when do you use it?
Destructuring allows me to unpack values from arrays or properties from objects into distinct
variables. It's a concise syntax that improves readability and reduces boilerplate when working
with structured data.
For example, when handling responses from APIs or when extracting specific values from deeply
nested objects, destructuring lets me write expressive and clear assignments.
I use it in function parameters, variable declarations, and loops — especially when I want to enforce
clarity about which part of the structure I’m accessing or when I want to set default values during
unpacking.
27. What is function currying and why would you use it?
Currying is a technique where a function is transformed into a series of functions, each accepting a
single argument. It allows partial application of functions and helps build reusable, configurable
logic.
Instead of calling a function with multiple arguments all at once, I can call it step by step. This is
especially useful in functional programming or when working with pipelines and transformations.
Currying improves code reusability and can lead to more declarative and composable code. I use it
when designing utility functions or creating abstractions that need to be flexible and adaptable to
different contexts.
28. What is the difference between immutability and mutability in JavaScript, and
how do you manage it?
Mutable data structures can be changed after they are created. Immutable data structures, by
contrast, cannot be changed — instead, any modification results in a new copy.
In JavaScript, objects and arrays are mutable by default, which can lead to side effects if I’m not
careful. That’s why I often work with immutable patterns, especially in React and Redux
development, where predictable state transitions are important.
I use the spread operator, Object.assign(), or libraries like Immer to create updated copies without
modifying the original. Practicing immutability helps avoid bugs caused by unintended data
changes and makes code easier to test and debug.
29. What are side effects in JavaScript and how do you avoid them?
A side effect is any change in state that happens outside of a function’s scope — like modifying a
global variable, updating the DOM, or making a network request.
In functional programming, side effects are discouraged because they make code harder to test and
predict. I aim to write pure functions whenever possible: functions that take input and return
output without modifying anything else.
When side effects are necessary (e.g., in event handlers or API calls), I isolate them, document them
clearly, and ensure they don’t unintentionally interfere with the rest of the codebase.
Performance optimization starts with measurement — I use browser dev tools to profile scripts,
monitor repaint and reflow times, and track memory usage. Once I identify bottlenecks, I apply
targeted improvements.
I also pay attention to network performance by optimizing bundle sizes, lazy loading assets, and
reducing third-party dependencies. Performance isn’t just about speed — it’s about responsiveness
and user experience.
Synchronous execution means tasks are performed one after another, blocking the main thread
until each completes.
Asynchronous code allows non-blocking behavior — operations like network requests or timers
don’t pause the rest of the code.
Concurrency is the ability to handle multiple tasks by interleaving them — in JavaScript, this is
achieved through the event loop, not true multithreading.
Understanding these distinctions is critical for writing responsive code. I use asynchronous
patterns (async/await, Promises) to prevent UI freezes and ensure efficient resource usage.
32. What are the differences between Object.create(), constructor functions, and
ES6 classes?
All three are ways to create objects with shared behavior through the prototype chain:
Object.create() creates an object with a manually assigned prototype — useful for flexible
inheritance patterns.
Constructor functions use the new keyword and add properties/methods to this or the
prototype.
ES6 classes are syntactic sugar over constructor functions, offering a cleaner, more familiar
syntax, especially for developers coming from OOP backgrounds.
In my code, I use ES6 classes for clarity and maintainability but also understand how they compile
down to prototype-based logic under the hood.
33. What is the module pattern and when would you use it?
The module pattern is a design pattern that encapsulates private variables and exposes public
APIs. It typically uses closures to keep internal state private, returning only the functions or objects
meant for external use.
Before ES6 modules were widely supported, I used this pattern to create isolated logic and avoid
polluting the global namespace. Even today, understanding the module pattern is essential for
working in legacy code or when manually managing dependencies.
34. What is tail call optimization (TCO), and does JavaScript support it?
Tail call optimization is a compiler feature where recursive function calls in the tail position don’t
add new stack frames. This prevents stack overflows and improves performance for deep
recursion.
Although the ES6 spec supports TCO in strict mode, most JavaScript engines (including V8) don’t
implement it yet. Because of that, I avoid deep recursion in performance-critical paths and prefer
iterative approaches or controlled recursion depth when necessary.
35. What are the main differences between ==, ===, and Object.is()?
== checks for equality with type coercion, which can lead to unexpected results.
=== checks for strict equality — both value and type.
Object.is() is almost like === but handles edge cases differently: it treats NaN === NaN as true,
and distinguishes +0 from -0.
I use === as the standard for comparisons and Object.is() only when precision around NaN or
signed zero is critical.
By default, listeners attach during bubbling. But I can register a listener in the capturing phase
using the { capture: true } option.
I use bubbling for delegation (e.g., attaching one listener to a parent), and capturing when I need to
intercept the event before it reaches the target — such as for global guards or logging.
When working with async/await, I wrap await calls in try/catch blocks to handle errors, and use
finally for cleanup logic that must always run (e.g., hiding a loading spinner).
For example, if an awaited function throws, control jumps immediately to catch. If there's a finally
block, it executes regardless of success or failure. This model gives me precise control over error
management and resource handling in asynchronous flows.
38. What is reflow and repaint in the browser, and how do they relate to
performance?
Reflow happens when the browser recalculates element positions and geometry. It's triggered
by DOM changes affecting layout.
Repaint happens when visual aspects like color or visibility change but layout stays the same.
Reflows are more expensive than repaints, especially when deeply nested elements are affected. I
minimize layout thrashing by batching DOM changes and reading/writing layout-related properties
separately. Tools like requestAnimationFrame also help me optimize visual updates.
I monitor memory consumption over time using Chrome DevTools’ Performance and Memory
panels. A memory leak usually shows up as steadily increasing heap size without cleanup after user
interactions.
I look for:
To fix them, I audit lifecycle hooks, use WeakMap/WeakSet where appropriate, and ensure listeners
are always removed when components are unmounted.
A service worker is a script that runs in the background, separate from the main browser thread. It
enables features like offline support, caching, and push notifications. It intercepts network
requests and can serve responses from a cache, making web apps feel more like native ones.
I use service workers when building Progressive Web Apps (PWAs) to improve performance and
reliability. They follow a lifecycle (install, activate, fetch), and since they’re async and persistent, I
always include robust error handling and versioning logic.