techlur logo techlur logo
BackendFrontendDatabaseAI & LLMsGeneral & HR Get free consultation

Frontend

JavaScript core, the browser, React, and CSS.

24 questions
Q. Explain var vs let vs const. easy

var is function-scoped and hoisted to the top of its function (initialized as undefined). It can be re-declared and reassigned.

let is block-scoped ({}). It is hoisted but not initialized, so accessing it before declaration throws a ReferenceError (the temporal dead zone). It can be reassigned but not re-declared in the same scope.

const is also block-scoped and must be initialized at declaration. It cannot be reassigned, but if the value is an object or array, the contents can still be mutated.

var a = 1;
var a = 2;   // OK — re-declaration allowed

let b = 1;
b = 2;       // OK — reassignment allowed
// let b = 3; // SyntaxError — no re-declaration

const c = { x: 1 };
c.x = 2;    // OK — mutation allowed
// c = {};   // TypeError — reassignment blocked

Rule of thumb: default to const, use let when you need reassignment, avoid var.

#javascript#variables
Q. What is a closure? medium

A closure is a function that remembers the variables from the scope where it was created, even after that outer function has returned. It’s the basis for data privacy and hooks like useState.

function counter() {
  let n = 0;
  return () => ++n; // closes over n
}
const next = counter();
next(); // 1
next(); // 2
#javascript#fundamentals
Q. What is hoisting? easy

Hoisting is JavaScript’s behavior of moving declarations to the top of their scope during the compile phase, before any code executes.

  • var declarations are hoisted and initialized as undefined, so you can reference them before the declaration line without an error.
  • let / const declarations are hoisted but not initialized. Accessing them before their declaration throws a ReferenceError — the region before initialization is called the temporal dead zone (TDZ).
  • Function declarations are fully hoisted (both name and body), so they can be called before they appear in the code.
  • Function expressions and arrow functions assigned to variables follow the rules of the variable keyword (var, let, or const).
console.log(x); // undefined (var is hoisted)
var x = 5;

console.log(y); // ReferenceError (TDZ)
let y = 10;

greet();        // "Hello!" (function declaration is fully hoisted)
function greet() {
  console.log("Hello!");
}
#javascript
Q. What does the this keyword refer to? medium

The value of this depends on how a function is called, not where it is defined.

Call stylethis value
Method call (obj.fn())The calling object (obj)
Plain call (fn())undefined in strict mode, globalThis in sloppy mode
new call (new Fn())The newly created instance
call / apply / bindWhatever you explicitly pass
Arrow functionInherited from the enclosing lexical scope (not its own this)
const user = {
  name: "Alice",
  greet() {
    console.log(this.name); // "Alice" — method call
  },
  greetLater() {
    setTimeout(() => {
      console.log(this.name); // "Alice" — arrow inherits this
    }, 100);
  },
};

function standalone() {
  console.log(this); // undefined (strict) or globalThis (sloppy)
}

Tip: Arrow functions are ideal for callbacks inside methods because they preserve the outer this.

#javascript
Q. Explain == vs ===. easy

== (loose equality) compares two values after type coercion. JavaScript converts one or both operands to a common type before comparing, which can produce surprising results.

=== (strict equality) compares both value and type with no coercion. If the types differ, the result is immediately false.

0 == "";       // true  — both coerced to 0
0 === "";      // false — number vs string

null == undefined;  // true  — special coercion rule
null === undefined; // false — different types

"1" == 1;      // true  — string coerced to number
"1" === 1;     // false — string vs number

Best practice: Always use === (and !==) to avoid accidental coercion bugs. The only common exception is value == null, which conveniently checks for both null and undefined.

#javascript
Q. What is the difference between shallow and deep copy? medium

A shallow copy duplicates only the top-level properties. Nested objects and arrays are still shared between the original and the copy, so mutating a nested value affects both.

A deep copy recursively duplicates everything, so no references are shared.

const original = { name: "Alice", address: { city: "NYC" } };

// Shallow copy — nested object is shared
const shallow = { ...original };
shallow.address.city = "LA";
console.log(original.address.city); // "LA" — both changed!

// Deep copy — fully independent
const deep = structuredClone(original);
deep.address.city = "Chicago";
console.log(original.address.city); // "LA" — original untouched

Common ways to copy:

MethodDepth
Spread ({ ...obj }) / Object.assignShallow
Array.from() / [...arr]Shallow
structuredClone(obj)Deep
JSON.parse(JSON.stringify(obj))Deep (but drops functions, undefined, Date objects, etc.)

Tip: structuredClone (available in all modern runtimes) is the safest built-in deep copy method.

#javascript#objects
Q. What are map, filter, and reduce? easy

These three array methods are the foundation of functional-style data transformation in JavaScript. None of them mutate the original array.

map — transforms every element and returns a new array of the same length.

[1, 2, 3].map(n => n * 2); // [2, 4, 6]

filter — returns a new array containing only elements that pass the test.

[1, 2, 3, 4].filter(n => n % 2 === 0); // [2, 4]

reduce — boils the array down to a single value by running an accumulator function on each element.

[1, 2, 3, 4].reduce((sum, n) => sum + n, 0); // 10

Chaining them together:

const orders = [
  { item: "Book", price: 12 },
  { item: "Pen", price: 2 },
  { item: "Bag", price: 35 },
];

const total = orders
  .filter(o => o.price > 5)     // keep expensive items
  .map(o => o.price)            // extract prices
  .reduce((sum, p) => sum + p, 0); // sum them
// total = 47
#javascript#arrays
Q. What is the Virtual DOM and how does React use it? medium

The Virtual DOM is a lightweight in-memory copy of the UI. On a state change, React builds a new tree, diffs it against the old one (reconciliation), and updates only the changed real-DOM nodes — far cheaper than re-rendering everything.

#react#rendering
Q. Explain useState and useEffect. medium

useState adds local state and returns the value plus a setter. useEffect runs side effects (data fetching, subscriptions) after render; its dependency array controls when it re-runs, and the returned function cleans up.

useEffect(() => {
  const id = setInterval(tick, 1000);
  return () => clearInterval(id); // cleanup
}, []); // [] = run once on mount
#react#hooks
Q. Explain debounce vs throttle. hard

Both techniques limit how often a function fires, but they work differently.

Debounce — waits until the user stops triggering the event for a set delay, then fires once. Each new trigger resets the timer.

Use case: search-as-you-type input, window resize handler.

function debounce(fn, delay) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

input.addEventListener("input", debounce(handleSearch, 300));

Throttle — fires at most once per interval, no matter how many times the event triggers. It guarantees a steady execution rate.

Use case: scroll listener, drag handler, rate-limited API calls.

function throttle(fn, interval) {
  let lastTime = 0;
  return (...args) => {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      fn(...args);
    }
  };
}

window.addEventListener("scroll", throttle(handleScroll, 200));

In short: Debounce collapses a burst into one call at the end. Throttle spreads calls evenly across time.

#javascript#performance
Q. localStorage vs sessionStorage vs cookies. easy

All three store data on the client, but they differ in lifetime, size, and behavior.

FeaturelocalStoragesessionStorageCookies
LifetimeUntil explicitly clearedUntil the tab/window closesSet by Expires / Max-Age (or session)
Size limit~5-10 MB~5-10 MB~4 KB per cookie
Sent to serverNoNoYes, on every matching HTTP request
ScopeSame origin, all tabsSame origin, single tabSame origin (configurable with Domain, Path)
APIsetItem / getItemsetItem / getItemdocument.cookie string or Set-Cookie header
// localStorage
localStorage.setItem("theme", "dark");
localStorage.getItem("theme"); // "dark"

// sessionStorage
sessionStorage.setItem("token", "abc123");

// Cookie
document.cookie = "lang=en; max-age=86400; path=/";

When to use what: Use cookies when the server needs the data (auth tokens with HttpOnly). Use localStorage for persistent user preferences. Use sessionStorage for temporary, tab-specific data.

#browser#storage
Q. What is event bubbling and delegation? medium

Event bubbling is the DOM’s default propagation model: when an event fires on an element, it first runs handlers on that element, then bubbles up through every ancestor to the document root.

click on <button>
  → button handler
    → div handler
      → body handler
        → document handler

You can stop propagation with event.stopPropagation(), but this is rarely needed.

Event delegation leverages bubbling by attaching a single listener on a parent instead of one listener on every child. The parent checks event.target to determine which child was actually clicked.

// Instead of adding a listener to every <li>...
document.querySelector("ul").addEventListener("click", (e) => {
  if (e.target.tagName === "LI") {
    console.log("Clicked:", e.target.textContent);
  }
});

Why delegation is useful:

  • Performance — one listener instead of hundreds.
  • Dynamic elements — new children added later are automatically handled.
  • Cleaner code — less setup and teardown logic.
#browser#events
Q. What happens when you type a URL and press Enter? hard

This classic question covers the full lifecycle of a web request:

  1. URL parsing — the browser parses the URL into protocol, host, port, and path.

  2. DNS resolution — the browser checks its cache, then the OS cache, then queries DNS servers to resolve the domain name to an IP address.

  3. TCP connection — a TCP three-way handshake (SYN, SYN-ACK, ACK) establishes a connection with the server.

  4. TLS handshake (HTTPS) — client and server negotiate encryption, exchange certificates, and establish a secure session.

  5. HTTP request — the browser sends the HTTP request (method, headers, cookies) to the server.

  6. Server processing — the server routes the request, runs application logic, queries databases if needed, and builds a response.

  7. HTTP response — the server sends back a status code, headers, and the response body (HTML).

  8. HTML parsing — the browser parses HTML into the DOM tree. When it encounters CSS, it builds the CSSOM tree. Scripts may block parsing unless marked async or defer.

  9. Render tree — the DOM and CSSOM are combined into a render tree (only visible elements).

  10. Layout — the browser calculates the exact position and size of every element.

  11. Paint — pixels are drawn to the screen, layer by layer.

  12. Compositing — layers are combined in the correct order and sent to the GPU for display.

This is a great question to show breadth of knowledge. Interviewers don’t expect every detail — focus on the areas you know best.

#browser#networking
Q. What are Core Web Vitals? medium

Core Web Vitals are a set of real-world performance metrics defined by Google that measure user experience on the web. They factor into search ranking.

MetricMeasuresGood threshold
LCP (Largest Contentful Paint)Loading — how quickly the main content appears< 2.5 s
INP (Interaction to Next Paint)Responsiveness — delay between user input and visual update< 200 ms
CLS (Cumulative Layout Shift)Visual stability — how much the page layout shifts unexpectedly< 0.1

How to improve each:

  • LCP — optimize images (modern formats, loading="lazy"), preload critical resources, use a CDN, reduce server response time.
  • INP — break up long tasks, use requestIdleCallback, minimize main-thread work, defer non-essential JavaScript.
  • CLS — set explicit width/height on images and embeds, avoid injecting content above existing content, use font-display: swap.

Tools to measure: Chrome DevTools (Lighthouse), PageSpeed Insights, web-vitals npm package, and Chrome UX Report (CrUX) for field data.

#performance#browser
Q. Props vs state. easy

Props (short for properties) are values passed from a parent component to a child. They are read-only — a child should never modify its own props.

State is data managed internally by a component. It can be updated with a setter (e.g., setState or the useState hook), and changes trigger a re-render.

PropsState
OwnerParent componentThe component itself
Mutable?No (read-only for the child)Yes (via setter function)
Triggers re-render?Yes, when the parent passes new valuesYes, when updated
PurposeConfigure a child from the outsideTrack internal, changing data
function Greeting({ name }) {       // name is a prop
  const [count, setCount] = useState(0); // count is state

  return (
    <div>
      <p>Hello, {name}!</p>
      <button onClick={() => setCount(count + 1)}>
        Clicked {count} times
      </button>
    </div>
  );
}

Key idea: Data flows down through props. When a child needs to communicate up, the parent passes a callback function as a prop.

#react
Q. What are keys in lists and why are they important? easy

Keys give React a stable identity for each item in a list. During reconciliation (diffing), React uses keys to figure out which items were added, removed, or reordered — without keys, React falls back to comparing by index, which can cause bugs and wasted renders.

// Good — unique, stable id
{users.map(user => (
  <UserCard key={user.id} name={user.name} />
))}

// Bad — array index as key
{users.map((user, index) => (
  <UserCard key={index} name={user.name} />
))}

Why array index is problematic:

  • If items are reordered, inserted, or deleted, the index shifts and React may reuse the wrong component instance, leading to stale state or broken animations.

Rules for good keys:

  • Use a unique, stable identifier from your data (database ID, slug, etc.).
  • Keys must be unique among siblings (not globally).
  • Never generate keys on the fly (e.g., Math.random()) — this defeats the purpose.

Tip: If your data has no natural ID, consider adding one at the data layer rather than relying on index.

#react
Q. useMemo vs useCallback — when do you use them? hard

Both hooks memoize values between renders to avoid unnecessary work, but they serve different purposes.

useMemo — memoizes a computed value. The factory function only re-runs when its dependencies change.

const sorted = useMemo(
  () => items.sort((a, b) => a.price - b.price),
  [items]
);

useCallback — memoizes a function reference. Useful when passing callbacks to child components wrapped in React.memo.

const handleClick = useCallback(() => {
  setCount(c => c + 1);
}, []); // stable reference across renders

useCallback(fn, deps) is essentially shorthand for useMemo(() => fn, deps).

When to use them:

  • An expensive calculation runs on every render (use useMemo).
  • A child component wrapped in React.memo receives a callback prop and re-renders unnecessarily (use useCallback).
  • A value is used as a dependency in another hook and needs referential stability.

When NOT to use them:

  • The computation is cheap — memoization itself has overhead (memory + comparison).
  • The component rarely re-renders anyway.
  • You’re optimizing prematurely without measuring.

Rule of thumb: Write code without memoization first. Add useMemo / useCallback only when profiling reveals a real performance issue.

#react#hooks#performance
Q. What is the rules-of-hooks constraint? medium

React hooks rely on a stable call order between renders. To guarantee this, React enforces two rules:

1. Only call hooks at the top level

Never call hooks inside loops, conditions, or nested functions. Every render must invoke the same hooks in the same order.

// Bad — conditional hook
if (loggedIn) {
  useEffect(() => { /* ... */ }); // breaks call order
}

// Good — condition inside the hook
useEffect(() => {
  if (loggedIn) { /* ... */ }
}, [loggedIn]);

2. Only call hooks from React functions

Hooks can only be called from:

  • Function components
  • Custom hooks (functions whose name starts with use)

They cannot be called from regular JavaScript functions, class components, or event handlers.

Why these rules exist:

React tracks hooks by their call index (first hook = index 0, second = index 1, etc.). If a hook is conditionally skipped, every subsequent hook shifts position and maps to the wrong state — causing subtle and hard-to-debug errors.

Tip: The eslint-plugin-react-hooks package (included in Create React App and Next.js) catches violations automatically.

#react#hooks
Q. How do you manage global state in React? medium

There is no single “right” answer — the best approach depends on the type and frequency of the shared data.

Context API (built-in)

Good for low-frequency, rarely-changing values like theme, locale, or auth status. Every consumer re-renders when the context value changes, so it is not ideal for rapidly updating state.

const ThemeCtx = createContext("light");

function App() {
  return (
    <ThemeCtx.Provider value="dark">
      <Page />
    </ThemeCtx.Provider>
  );
}

Zustand / Redux / Jotai (external libraries)

Better for larger, frequently-changing state. These libraries provide selectors so components only re-render when the specific slice of state they use changes.

// Zustand example
const useStore = create((set) => ({
  count: 0,
  increment: () => set((s) => ({ count: s.count + 1 })),
}));

React Query / TanStack Query (server state)

Best for data fetched from APIs. Handles caching, background refetching, stale-while-revalidate, and error/loading states out of the box — keeping server state out of your global store.

Guideline: Start with local state (useState) and lift only when needed. Reach for Context for simple global values, a dedicated store for complex client state, and a data-fetching library for server state.

#react#state-management
Q. What causes unnecessary re-renders and how do you prevent them? hard

A React component re-renders when:

  1. Its state changes.
  2. Its props change (by reference).
  3. Its parent re-renders (even if the child’s props are the same).
  4. A context it consumes changes.

Most re-renders are fast and harmless, but they become a problem in large lists or computation-heavy components.

Prevention strategies:

React.memo — wraps a component so it only re-renders when its props change (shallow comparison).

const ExpensiveList = React.memo(({ items }) => {
  return items.map(item => <Row key={item.id} {...item} />);
});

useCallback / useMemo — stabilize callback and object references passed as props so React.memo comparisons succeed.

const handleClick = useCallback(() => { /* ... */ }, []);
const config = useMemo(() => ({ theme: "dark" }), []);

Component splitting — move state as close to where it’s used as possible. Only the component holding the state re-renders.

// Before: entire page re-renders on every keystroke
// After: only SearchInput re-renders
function SearchInput() {
  const [query, setQuery] = useState("");
  return <input value={query} onChange={e => setQuery(e.target.value)} />;
}

Store selectors — with Zustand or Redux, subscribe to specific slices so unrelated state changes don’t trigger a re-render.

Key principle: Profile first (React DevTools Profiler), optimize second. Premature memoization adds complexity without measurable benefit.

#react#performance
Q. CSR vs SSR vs SSG (and where does Next.js fit)? hard

These are three strategies for turning your components into HTML that the browser can display.

CSR (Client-Side Rendering)

The server sends a nearly empty HTML shell and a JavaScript bundle. The browser downloads, parses, and executes the JS to render the page. Good for highly interactive apps (dashboards), but the initial load can be slow and SEO is limited.

SSR (Server-Side Rendering)

The server renders the full HTML on every request and sends it to the browser. The page is visible immediately, then JS “hydrates” it to make it interactive. Great for SEO and dynamic, personalized pages, but every request hits the server.

SSG (Static Site Generation)

HTML is generated at build time and served from a CDN. Fastest possible load since there is no per-request work. Ideal for content that does not change often (blogs, docs, marketing pages).

CSRSSRSSG
RenderedIn the browserOn the server per requestAt build time
Time to first contentSlow (JS must load)FastFastest
SEOPoor (without extra work)GoodGood
Dynamic dataYesYesLimited (rebuild needed)

Where Next.js fits:

Next.js lets you choose the rendering strategy per route. A single app can mix SSG pages, SSR pages, and fully client-rendered components. It also supports ISR (Incremental Static Regeneration) — static pages that revalidate in the background after a set interval, combining the speed of SSG with fresher data.

#react#nextjs#rendering
Q. Flexbox vs Grid. easy

Both are CSS layout systems, but they solve different problems.

Flexbox is one-dimensional — it lays out items along a single axis (row or column). It excels at distributing space and aligning items within a line.

.nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

Grid is two-dimensional — it controls both rows and columns simultaneously. It is ideal for page-level layouts and any design that needs alignment in both directions.

.dashboard {
  display: grid;
  grid-template-columns: 250px 1fr;
  grid-template-rows: auto 1fr auto;
  gap: 1rem;
}
FlexboxGrid
AxesOne (row or column)Two (rows and columns)
Best forNavbars, toolbars, card rows, centeringPage layouts, dashboards, galleries
Content vs layoutContent-driven (items size themselves)Layout-driven (you define the grid, items fill it)

In practice: You often use both. Grid for the overall page structure and Flexbox for component-level alignment within each grid cell.

#css#layout
Q. Explain the box model and box-sizing. easy

Every HTML element is a rectangular box made up of four layers (from inside out):

  1. Content — the actual text, image, or child elements.
  2. Padding — transparent space between the content and the border.
  3. Border — the visible edge around the padding.
  4. Margin — transparent space outside the border, separating the element from its neighbors.

box-sizing property:

By default (content-box), width and height apply only to the content area. Padding and border are added on top, making the element larger than the declared size.

With border-box, width and height include content + padding + border. This makes sizing far more predictable.

/* Apply border-box globally — recommended */
*, *::before, *::after {
  box-sizing: border-box;
}

.card {
  width: 300px;
  padding: 20px;
  border: 2px solid #ccc;
  /* With border-box: total width is still 300px */
  /* With content-box: total width would be 300 + 40 + 4 = 344px */
}

Best practice: Set box-sizing: border-box on all elements at the start of your stylesheet. Nearly every modern CSS reset or framework does this.

#css
Q. How does CSS specificity work? medium

When multiple CSS rules target the same element, the browser uses specificity to decide which rule wins. Specificity is calculated as a three-part score: (A, B, C).

LevelWhat countsExample
A — IDs#header(1, 0, 0)
B — Classes, attributes, pseudo-classes.nav, [type="text"], :hover(0, 1, 0)
C — Elements, pseudo-elementsdiv, ::before(0, 0, 1)

Comparing specificity: A beats B beats C, regardless of count.

/* (0, 1, 0) — one class */
.button { color: blue; }

/* (1, 0, 0) — one ID (wins) */
#submit { color: red; }

/* (0, 0, 2) — two elements (loses to one class) */
div button { color: green; }

Tie-breaker: When two selectors have equal specificity, the later rule in the stylesheet wins.

Special cases:

  • Inline styles (style="...") override any selector-based rule.
  • !important overrides everything, including inline styles. Use it sparingly.
  • The universal selector (*), combinators (>, +, ~), and :where() add zero specificity.
  • :is() and :not() take the specificity of their most specific argument.

Tip: Keep specificity low and flat. Prefer classes over IDs, avoid !important, and use a consistent naming convention (like BEM) to prevent specificity wars.

#css