Back to Developer

Frontend Development

Frontend interview questions — React, TypeScript, and the browser.

What is the difference between Component and PureComponent in React? Give an example where PureComponent might break an app.
Component re-renders whenever setState is called or a parent re-renders, regardless of whether props/state actually changed. PureComponent implements shouldComponentUpdate with a shallow comparison of props and state, skipping re-renders when references haven't changed. This can break an app when you mutate objects or arrays in place (e.g., pushing to an array without creating a new reference): PureComponent won't detect the change and the UI becomes stale.
Why can combining React Context with shouldComponentUpdate be dangerous?
shouldComponentUpdate (or PureComponent) can block re-renders of intermediate components in the tree. If a context value changes but an ancestor returns false from shouldComponentUpdate, its children won't re-render even though they consume that context. This means context consumers may display stale data. The modern Context API (React 16.3+) mitigates this by propagating directly to consumers, but legacy context was fully susceptible to this issue.
Describe three ways to pass information from a child component to its parent in React.
1) Callback props – the parent passes a function as a prop; the child invokes it with data. 2) Lifting state up / shared context – move state to the parent (or a common context provider) and let both components access it. 3) Refs – the parent creates a ref (useRef / React.createRef) and passes it to the child; the child can attach imperative handles (useImperativeHandle) or the parent reads DOM values directly.
Give two ways to prevent a React component from re-rendering unnecessarily.
1) React.memo (for function components) or PureComponent (for class components) – wraps the component so it only re-renders when its props change by shallow comparison. 2) useMemo / useCallback – memoize expensive computations or callback references so downstream components receiving them as props don't see new references on every render and therefore skip re-rendering.
What is a React Fragment and why do we need it? Give an example where it might break an app.
A Fragment (<React.Fragment> or <> </>) lets you group multiple elements without adding an extra DOM node. It's needed because JSX expressions must return a single root element. It can break an app if you rely on direct parent-child DOM relationships for CSS (e.g., Flexbox or Grid layouts) – replacing a wrapping <div> with a Fragment removes the container, and children end up as direct children of a grandparent element, disrupting the intended layout.
Give three examples of the Higher-Order Component (HOC) pattern in React.
1) withRouter (React Router) – injects route props (history, location, match) into a wrapped component. 2) connect (Redux) – maps store state and dispatch to component props. 3) A custom withAuth HOC that checks authentication and either renders the wrapped component or redirects to a login page. HOCs accept a component and return a new enhanced component, promoting reuse of cross-cutting logic.
How many arguments does setState take in React and why is it asynchronous?
setState takes up to two arguments: 1) an updater – either an object to shallow-merge into state, or a function (prevState, props) => newState for updates that depend on current state; 2) an optional callback executed after the state has been applied and the component re-rendered. It is asynchronous (batched) for performance: React groups multiple setState calls into a single re-render pass to avoid unnecessary intermediate renders and layout thrashing.
What steps are needed to migrate a React Class Component to a Function Component?
1) Replace the class declaration with a function that receives props as an argument. 2) Remove the constructor; convert this.state to useState hooks. 3) Replace lifecycle methods (componentDidMount, componentDidUpdate, componentWillUnmount) with useEffect hooks. 4) Remove all references to 'this'. 5) Convert class methods to local functions or use useCallback. 6) Replace this.props with destructured props parameter. 7) If using context, switch from static contextType to useContext. 8) If using refs, replace createRef with useRef.
List a few ways styles can be used with React components.
1) Inline styles via the style prop (a JS object with camelCase properties). 2) CSS/SCSS files imported directly into the component file. 3) CSS Modules (locally scoped class names via *.module.css). 4) CSS-in-JS libraries (styled-components, Emotion) that generate scoped styles at runtime. 5) Utility-first frameworks like Tailwind CSS applied via className strings.
How do you render an HTML string coming from the server in React?
Use the dangerouslySetInnerHTML prop: <div dangerouslySetInnerHTML={{ __html: htmlString }} />. The name is intentionally alarming because injecting raw HTML is an XSS risk. Always sanitize the string server-side or with a trusted library (e.g., DOMPurify) before rendering. Alternatively, parse the HTML into a React element tree using a library like html-react-parser if you need component-level control.
How would you implement text highlighting of matching characters in a React autocomplete component?
Split the suggestion text into parts based on the search query using a case-insensitive regex or string index matching. Render non-matching parts as plain text and wrap matching substrings in a <mark> or <span> with a highlight CSS class. For example: text.split(new RegExp((${escaped query}), 'gi')).map(part => part matches ? <strong>{part}</strong> : part). Be sure to escape regex special characters in the query.
What edge cases should you handle in a React autocomplete component for a perfect user experience?
Key edge cases include: debouncing input to avoid excessive API calls, handling empty results gracefully, keyboard navigation (arrow keys, Enter, Escape), closing the dropdown on blur/outside click, handling rapid typing that causes out-of-order async responses (race conditions), trimming whitespace, showing a loading indicator, preventing form submission on Enter when selecting, and accessibility (ARIA attributes, screen reader announcements).
Why should the data-fetching function in an autocomplete component be asynchronous even when using local mock data?
Making the filter function asynchronous simulates real-world conditions where data comes from a network request with latency. This forces you to handle loading states, race conditions (stale responses arriving after newer queries), and component unmounting during pending requests. It also makes the component ready to swap mock data for a real API without architectural changes.
How can you handle race conditions in a React autocomplete that fetches suggestions asynchronously?
Use a cleanup mechanism so that only the latest request's result is applied. In a useEffect, return a cleanup function that sets a cancelled flag; when the async response arrives, check the flag before updating state. Alternatively, use an AbortController to cancel previous fetch calls, or maintain a request ID counter and only apply results whose ID matches the latest dispatched request.
Why does React require that you use only functional components with hooks for a modern component?
Functional components with hooks provide a simpler mental model: no 'this' binding issues, cleaner separation of concerns via custom hooks, easier testing, and better alignment with React's concurrent features. Hooks like useState, useEffect, useRef, and useMemo cover all class lifecycle patterns in a composable way. The React team recommends hooks for all new code because they reduce boilerplate and encourage logic reuse without HOCs or render props.
How do you handle API failures gracefully in a React frontend?
Wrap the fetch/axios call in a try-catch block and maintain an error state variable. If the request fails or returns a non-2xx status, set the error state and render a user-friendly error message instead of the results. Also disable the submit button or show a loading spinner while the request is in flight.
What are the key considerations for making a data-input form responsive and user-friendly in React?
Use controlled components so the form state always reflects the UI, provide clear labels and placeholder text, give immediate validation feedback, and handle loading and error states with visible indicators. For a matrix input, a textarea with monospace font makes the grid easier to read.
How do you implement role-based protected routes in a React SPA without a real backend?
You maintain a simulated auth state (e.g., in React Context or a global store, persisted in sessionStorage/localStorage) that stores the selected role. A ProtectedRoute wrapper component checks the current role; if the required role is absent it redirects to the login screen using <Navigate>. Each role tree (/taller/..., /cliente/...) is wrapped in its own guard, so direct URL access is always intercepted.
What is the Container/Presenter pattern in React and why is it valuable?
Container (smart) components own state and business logic and pass data down via props. Presenter (dumb) components are pure rendering units with no side effects. The split improves testability (presenters are easily unit-tested), reusability (a presenter can be driven by different containers), and readability because UI concerns are fully separated from domain concerns.
How would you implement a finite state machine for an order lifecycle in React?
Define an allowed-transitions map (e.g., { CREATED: ['DIAGNOSED','CANCELLED'], DIAGNOSED: ['AUTHORIZED','CANCELLED'], ... }). Expose a transition(order, newStatus) function in a pure domain module that validates the move against the map, appends an Event record, and returns a new order object. React state is updated by replacing the order in the array; the UI derives which action buttons to show from the current status.
How do you keep React state and localStorage in sync reliably?
Use a custom hook (e.g., useLocalStorage) that initialises state by reading from localStorage and wraps the setter so every update also calls localStorage.setItem. For complex object graphs, serialise with JSON.stringify/JSON.parse. Seed default data once on first load by checking whether the key is absent. This pattern ensures a page refresh always hydrates the correct state without extra effects scattered across components.
How do you apply SOLID's Open/Closed Principle when adding new order statuses or business rules to a React frontend?
Keep each business rule in its own function or class in a domain layer (e.g., src/domain/orders/). Adding a new status or rule means adding a new entry to the transitions map and a new validator function — existing functions are untouched. UI components only consume the domain API, so they require no changes either. This avoids modifying proven code and limits the blast radius of new requirements.
What does 'hexagonal architecture applied to the frontend' mean in practice?
The domain layer (pure business logic, no React imports) sits at the centre. Adapters surround it: UI adapters are React components and hooks that call domain functions; persistence adapters are modules that read/write localStorage. The domain never imports from React or localStorage — it only works with plain data. This makes the domain independently testable and swappable (e.g., replace localStorage with IndexedDB without touching business rules).
How do you handle and surface business-rule violations (e.g., NO_SERVICES, REQUIRES_REAUTH) in a React UI without crashing the application?
Business-rule violations should be returned as typed error objects from domain functions rather than thrown exceptions. The calling hook or reducer appends them to an errors array on the order and updates the UI state. Components render an error list or toast based on that array. Because execution continues normally, the app never crashes; errors are part of the domain model and are visible in the order's history.
How do you implement an immutable event/audit trail for domain entities in a frontend application?
Each mutation produces a new Event record (with id, type, fromStatus, toStatus, timestamp) and appends it to the order's events array. The array is never modified in-place; instead a new order object with the extended events list is returned and stored. This creates a full, chronological log of what happened to the order that both the workshop and the client can inspect, and it is trivially testable because the trail is pure data.
What is mobile-first design and how does it affect CSS and component decisions in a React project?
Mobile-first means writing base styles for the smallest viewport and layering overrides for larger screens using min-width media queries. In React this influences layout choices (flexbox stacks over grids for mobile, progressive disclosure for dense data), table handling (collapsing to card lists on small screens), and navigation patterns (bottom tab bars or hamburger menus instead of sidebars). Starting small forces prioritisation of the most critical UI elements.
How would you implement the 110% cost-overrun guard as a reusable, testable function in the domain layer?
Write a pure function checkCostOverrun(order): BusinessError | null that computes limit = order.authorizedAmount * 1.10 and compares it to order.realTotal. If realTotal > limit it returns a REQUIRES_REAUTH error object; otherwise it returns null. This function is called after any cost update and on the IN_PROGRESS → COMPLETED transition. Because it is pure, it can be unit-tested with a simple value fixture without mounting any component.
How do you structure a React project using domain-based module organisation?
Group files by domain slice rather than by technical type. For example: src/domain/orders/ (types, state machine, business rules), src/domain/clients/ (types, queries), src/domain/auth/ (role context, guards), src/shared/ (UI primitives, hooks, utils). Each domain module exports a public API and hides internal details. This keeps related code co-located, makes deletions safe, and scales well as new domains are added.
How do you unit-test business rules (state transitions, cost calculations) in a React project that uses no backend?
Extract all business logic into pure functions or classes in the domain layer with zero React dependencies. Tests import these functions directly and assert against returned values or error codes. For example: expect(transition(order, 'AUTHORIZED')).toEqual({ ...order, status: 'AUTHORIZED' }). Vitest or Jest are typical choices. This separation means tests run instantly without jsdom, React Testing Library, or any component mounting.
How do you calculate and format the authorised amount including VAT in a JavaScript frontend?
Compute authorizedAmount = Math.round(subtotalEstimated * 1.16 * 100) / 100 to avoid floating-point drift and produce a value with 2 decimal places. For display, use Intl.NumberFormat with the appropriate locale and currency option rather than manual string concatenation. Keep the raw numeric value in state and only format at render time so comparisons and calculations always work on numbers.
What strategies can you use to manage global state in a mid-size React SPA without introducing Redux?
React Context paired with useReducer is the most common lightweight approach: a context holds the state (orders, clients, auth role) and a dispatch function exposes typed actions. For better performance, split contexts by concern (AuthContext, OrderContext) to avoid re-rendering unrelated components. Custom hooks (useOrders, useAuth) abstract the context consumption. If complexity grows, Zustand is a minimal alternative that avoids boilerplate.
How do you design a domain model for a frontend SPA so that each entity references others by ID rather than by embedding full objects?
Store each entity type in its own normalised collection (e.g., customers: Record<string,Customer>, orders: Record<string,RepairOrder>). Entities reference each other via IDs (customerId, vehicleId). UI components receive the full object graph only when needed, assembled in a selector or hook. This mirrors what a backend API would return, avoids duplicated data, and makes single-entity updates O(1) with no cascade copies.
How do you ensure that business rules cannot be bypassed by manipulating the UI directly (e.g., clicking a hidden button)?
Business rules must live in the domain layer, not only in UI conditionals that hide buttons. Every state-mutating action goes through a domain function that re-validates preconditions before producing a new state. The UI disabling/hiding a button is a convenience; the authoritative guard is always in the domain function. For a frontend-only app, localStorage can be tampered with, so on load, validate/sanitise data coming out of storage before using it.
What React hook patterns are most useful for encapsulating order-lifecycle logic?
useReducer is ideal for complex state with many transitions — each action type maps to a domain function call. Wrap the reducer and dispatch in a useOrders custom hook that also syncs to localStorage on every dispatch. useMemo can derive computed values (e.g., filtered order list, 110% limit) from raw state without re-running on every render. useCallback stabilises event handlers passed to child components to prevent unnecessary re-renders.
What is Cross-Site Scripting (XSS) and how do you prevent it?
XSS is a vulnerability where an attacker injects malicious client-side scripts into a page viewed by other users, enabling cookie theft, session hijacking, or DOM manipulation. The fundamental rule is to never trust user input. Key mitigations: escape/encode all output before rendering, enforce a Content-Security-Policy header to restrict script sources, avoid innerHTML in favour of textContent, and use frameworks that auto-escape (React does this by default).
What is Clickjacking and how do you defend against it?
Clickjacking (UI Redress Attack) tricks a user into clicking an element hidden under a transparent overlay. The attacker embeds your page in an iframe and places their own UI on top. The primary defenses are the X-Frame-Options HTTP header (set to DENY or SAMEORIGIN) or the Content-Security-Policy frame-ancestors directive, both of which prevent your page from being embedded in iframes on other origins.
Is it safe to store authentication tokens in localStorage or sessionStorage, and where should they be stored?
No. Both storages are readable by any JavaScript running on the page, making tokens vulnerable to XSS. The recommended approach is HttpOnly cookies: the browser sends them automatically with same-origin requests but JavaScript cannot read them, eliminating the XSS vector. As a trade-off, CSRF protection (SameSite=Strict or a CSRF token) must also be implemented.
What is React Fiber and why was it introduced?
React Fiber is a complete rewrite of React's reconciliation algorithm, shipped in React 16. Before it, reconciliation was synchronous and recursive and could not be interrupted, causing janky UIs with large trees. Fiber represents each unit of work as a linked-list node, letting React break rendering into chunks, pause, prioritise, and resume. This enabled Concurrent Mode features like Suspense, error boundaries, and hooks.
How does React Reconciliation work?
When state changes, React builds a new virtual DOM tree and diffs it against the previous one using a heuristic O(n) algorithm. It compares element types first—if the type changes, React destroys and rebuilds the entire subtree. If the type is the same it updates only changed attributes. For lists it uses the key prop to match old and new items, allowing stable items to be reused without re-mounting.
Why is the key prop important in React lists, and why is using the array index as key a bad practice?
The key prop lets React's reconciler identify which items have changed, been added, or removed without re-mounting unchanged ones. Using the array index as key is problematic when the list is reordered, filtered, or items are inserted or deleted: React reuses the wrong component instances, leading to incorrect state and unnecessary re-renders. A stable, unique identifier (e.g., a database ID) should be used instead.
What is a Higher-Order Component (HOC) in React?
A HOC is a function that takes a component and returns a new component with additional props or behaviour injected—an application of the decorator pattern. HOCs enable reuse of cross-cutting concerns such as authentication guards or analytics tracking without modifying the wrapped component. They have largely been superseded by custom hooks, which are simpler and avoid issues like prop collision and opaque component trees.
What are React Portals and when should you use them?
Portals render a child component into a DOM node that exists outside the parent component's DOM hierarchy while keeping the child inside the React component tree—so context and event bubbling still work normally. Use them for UI elements that must visually escape overflow:hidden or z-index stacking contexts: modal dialogs, tooltips, popovers, and notifications that need to render at document body level.
What problem does useTransition (or startTransition) solve in React Concurrent Mode?
They address the blocking-render problem by marking state updates as non-urgent. React can interrupt such an update if more urgent work—like a keypress—arrives, keeping the UI responsive during expensive re-renders. useTransition additionally exposes an isPending boolean so you can show a loading indicator while the transition is in progress.
If a single event handler calls setState three times, how many renders will React trigger?
In React 18, just one, because automatic batching groups all state updates within any asynchronous context (setTimeout, Promises, native event handlers) into a single re-render. Before React 18, batching was limited to React's synthetic event handlers, so three setState calls outside that context could trigger up to three separate renders.