cswork / libraryOpen practice workspace

Interview guides · Anteelo

Anteelo Software Engineer Interview Guide

Anteelo Software Engineer interview guide: the two reported rounds, React and full-stack questions with answer approaches, and a 7-day prep plan.

The 60-second brief

Anteelo builds digital solutions for clients, and candidates describe the Software Engineer role as application work centred on the React ecosystem. The role often spans the full stack: user interfaces on the front end, backend services and the APIs between them. Engineers are also described as taking part in code reviews, handling production issues and working with product managers to turn business requirements into technical specifications. Senior candidates are described as mentoring junior developers and joining architecture discussions.

This guide is for candidates interviewing for a Software Engineer role at Anteelo, with the emphasis on React, JavaScript or TypeScript and full-stack work. It covers the two reported rounds, the nine reported questions (React performance, lifecycle, state, async data, reusable components, API contracts, maintainability, security and a client-plus-server bottleneck story) and a one-week plan. Where a question asks for past experience, it tells you what the story has to show.

Candidates report two rounds, an Initial Screening followed by Deep-Dive Technical Rounds, over roughly 2-4 weeks, with the pace varying by seniority.

Focus areas

  • React
  • JavaScript (Web Development)
  • Full Stack Development

Overview

The reported questions cluster in two areas. The first is React and front-end engineering: rendering performance in a large application, functional versus class components and their lifecycles, state-management pitfalls, async data fetching with error boundaries, and building reusable components. The second is full-stack judgment: API contract design, maintainable code as a project grows, security practice, and a past bottleneck that crossed client and server. A candidate who can only recite hook names will struggle with the second group, and a candidate who only knows backend design will struggle with the first.

Candidates also describe the role's requirements as React plus JavaScript or TypeScript, RESTful APIs and web performance, with Node.js and Express, CI/CD pipelines and AWS or Azure listed as nice-to-haves. Treat those as the vocabulary of your answers: when you discuss an API contract, talk about REST resources and TypeScript types; when you discuss maintainability, mention how tests and a CI pipeline protect a growing codebase.

Candidates report that the emphasis is on practical application development rather than competitive-programming puzzles, so spend your hours on building and explaining components, reading a profiler, and defending design choices. Practise saying the trade-off out loud (Context versus Redux, memoisation versus simpler code, strict types versus a flexible payload), because the reported advice for this loop is that explaining trade-offs matters as much as writing code quickly.

The Anteelo interview process

Candidates report two rounds, Initial Screening and then Deep-Dive Technical Rounds, over roughly 2-4 weeks; seniority can speed up or reshape the stages.

Initial Screening

The Initial Screening is reported as a first conversation about your technical background and your interest in Anteelo. Candidates also mention using the initial recruiter call to confirm location and remote requirements for the team. Treat it as a short, structured pitch: the React and full-stack work you have shipped, what you owned, and why this company's client-facing application work appeals to you.

What to demonstrate

  • Relevance of your background to React, JavaScript or TypeScript and full-stack work
  • Your stated interest in the company and the role
  • Practical fit on logistics such as location and remote working

How to prepare

  • Prepare a short walk through two or three projects that used React, naming the state approach, the API layer and one performance or reliability problem you handled.
  • Write down why this role appeals to you, tied to client-facing application work rather than generic enthusiasm.
  • List your constraints on location, remote work and start date, and ask the recruiter to confirm the shape of the remaining round or rounds.

Deep-Dive Technical Rounds

The Deep-Dive Technical Rounds are reported as practical coding and architecture discussion rather than abstract puzzles. Reported example scenarios in the React area include structuring a complex dashboard with nested components and handling memory leaks in a large React app. Prepare to write clean React and JavaScript or TypeScript while talking through your reasoning, then widen out to API design, maintainability and security.

What to demonstrate

  • Working React and JavaScript or TypeScript code, including hooks and component structure
  • Architectural reasoning about state, data flow and the client-server boundary
  • Your ability to explain trade-offs between approaches

How to prepare

  • Build a small dashboard with nested components and decide, aloud, where state lives and why: local state, lifted state, Context or Redux.
  • Write a data-fetching hook that cancels on unmount and handle its error state, then wrap the tree in an error boundary with a retry.
  • Sketch a REST API contract in TypeScript for one resource, including error shapes and pagination, and list the security checks you would add on both client and server.

Where candidates lose points

Answering the React performance question with a list of useMemo, useCallback and React.memo without ever measuring

Start from the profiler: find which components re-render and why, then fix the cause (state placed too high, unstable props, a huge unvirtualised list, an oversized bundle). Say that memoisation has its own cost and should follow measurement.

Claiming an error boundary will catch a failed fetch or an exception in an event handler

Know the limits: boundaries catch render and lifecycle errors in the subtree only. Show how async failures are put into state and either rendered inline or rethrown during render so a boundary can handle them.

Treating hooks as a one-to-one rename of class lifecycle methods

Explain useEffect in terms of synchronisation and cleanup, with dependency arrays, and point out what a class could not do easily: share stateful logic through custom hooks. Be ready to convert a componentDidMount, componentDidUpdate and componentWillUnmount trio into one effect with a cleanup.

Answering the API contract question with just 'use TypeScript'

Types vanish at runtime. Describe the contract for a concrete resource: typed request and response shapes, validation of incoming data at the server boundary, a consistent error format, pagination and a plan for adding optional fields without breaking existing callers.

Telling the client-plus-server bottleneck story without measurements or your own role in it

Write the story out with the symptom, how you separated client, network and server time, the fix, and before-and-after numbers. Say 'I' for what you did and name teammates only for what they did.

Questions & practice

Archive a resource graph without breaking live references or recursing

Medium · graph traversal · topological order · tenant isolation

Resources reference other resources within a tenant; for the largest tenant the reference table holds up to 2,000,000 nodes and 8,000,000 edges. Archiving a resource must archive everything reachable from it that nothing outside the set still references, refuse when a live external referrer exists, and terminate when references form cycles, which they legitimately do. Produce the archive order and the refusal list, targeting O(V+E). Say what stops the traversal crossing a tenant boundary, and why recursion is the wrong control structure at this size.

Approach

  1. Load the subgraph with the tenant predicate on both endpoints of the edge, not only on the side you started from. Scoping the left table alone is the classic cross-tenant leak: one mis-entered edge then pulls another tenant's resources into the traversal and, worse, into the archive.
  2. Traverse iteratively with an explicit stack. A 2,000,000-node graph can hold a chain deep enough to exhaust a native stack in the low tens of thousands of frames, and that failure is a process crash rather than an error you can return.
  3. Treat cycles as data rather than corruption: compute strongly connected components with Tarjan in O(V+E) using its own explicit stack, then condense. The condensation is a DAG, so a topological order over it gives the archive order, and every member of a component archives in one transaction because no order within a cycle is valid.
  4. Decide refusals with reverse edges. A candidate is archivable only if every in-edge originates inside the candidate set, so build the transpose or count in-degrees restricted to the visited set, and emit each blocked resource with the id of the external referrer, which is the only part of the answer an operator can act on.

Follow-up

  • The graph is read in one query and the archive writes a minute later. What can change in between, and how do you make the write safe?
  • The candidate set is 400,000 resources. Is that one transaction, and if not, what does a half-finished archive look like to a reader?

Canonicalise a request body into a stable idempotency fingerprint

Medium · parsing · canonicalisation · hashing

idempotency_key.request_fingerprint is a SHA-256 over the method, path and canonicalised body, and a retry whose fingerprint differs must be rejected with 422 rather than served the stored response. Write the canonicaliser. Bodies are JSON up to 256 KB nested at most 32 levels; clients vary key order, whitespace and unicode escaping, and some send 64-bit ids as JSON numbers. Produce a deterministic byte string such that semantically identical bodies match and any semantic difference does not. State your complexity and name two normalisations you refuse to perform.

Approach

  1. Parse once into a tree, then re-serialise under fixed rules: object keys sorted, array order preserved, one escaping convention, no insignificant whitespace. Parsing is O(n) and sorting keys is O(k log k) per object, so O(n log n) overall with O(depth) stack, and the 32-level cap is enforced during parsing because hostile nesting is how a canonicaliser becomes a stack overflow.
  2. Sort keys by their UTF-8 bytes and say why the obvious implementation is wrong in some runtimes: a default string comparison that orders by UTF-16 code units places surrogate pairs, meaning code points from U+10000 up, below U+E000 to U+FFFF, which is not UTF-8 byte order, so two services written in different languages disagree on the same document.
  3. Do not re-encode numbers through a double. IEEE-754 binary64 represents integers exactly only up to 2^53, so normalising a 19-digit id through a float changes it, and 1 against 1.0 cannot be reconciled without deciding whether they are the same value. Preserve the literal token, and require ids as strings at the API boundary if you want them comparable.
  4. Reject duplicate keys rather than picking one. JSON permits them and parsers disagree, most keeping the last, so any choice you make ties the fingerprint to a parser detail that the code handling the request does not necessarily share.

Follow-up

  • A client sends the same logical request with an extra field your API ignores. Same key, different fingerprint, so you return 422. Is that the right answer?
  • Where does the fingerprint get computed relative to request decompression and the body-size limit?

Identify the heaviest tenants in a five-minute window under memory pressure

Medium · top-k · heavy hitters · streaming

The edge service handles about 3,000 requests per second across roughly 50,000 tenants, peaking near 9,000. Expose the 50 heaviest tenants by request count over the trailing five minutes so limits can be tightened before one tenant's backfill starves the fleet. You may not retain five minutes of raw records. Give the exact solution and its memory, then the bounded-memory approximation with its error stated as a formula, and say which you would ship and at what tenant cardinality that choice changes.

Approach

  1. Do the exact version first, because it is affordable at this cardinality: a ring of 300 one-second counters per tenant, advanced lazily, is 1,200 bytes of counters per tenant and roughly 60 to 90 MB for 50,000 tenants with overhead. Carry a running total and subtract the bucket you overwrite so a window read is O(1) rather than 300 adds.
  2. Extract the top 50 with a size-k min-heap over the tenant sums: O(d log k) for d tenants, against O(d log d) to sort them all. Maintaining the heap continuously instead of on query requires a tenant-to-heap-index map, because incrementing a count already inside the heap means sifting from a known position, and without that map you rebuild the heap on every request.
  3. State the approximation precisely rather than gesturing at sketches. Misra-Gries with m counters retains every item whose true count exceeds N/(m+1), and each retained count underestimates the truth by at most N/(m+1). With m = 1,000 and N = 900,000 requests in the window the error is roughly 900 requests, which is fine for spotting a tenant sending 50,000 and useless for ranking two tenants 200 apart.
  4. Say what breaks when the window slides: Misra-Gries and Space-Saving are insert-only and cannot be decremented as records age out. The workable construction is one summary per sub-window, say ten seconds, with 30 summaries merged at query time, and the merged error is the sum of the per-summary errors, so the bound degrades linearly in the number of sub-windows.

Follow-up

  • The heaviest tenant is heavy because of one export job rather than user traffic. Should the limiter treat those as the same tenant?
  • Two tenants sit tied at the boundary of the top 50. Does your answer flap, and does the flapping matter?

Hold a per-tenant active cap against concurrent creates

Hard · write skew · isolation levels · constraints

A tenant on the standard plan may hold at most 50 resources with status='active'. The create handler runs SELECT count(*) FROM resource WHERE tenant_id = $1 AND status = 'active', compares to 50, then inserts. Two creates arrive 3 ms apart on different instances and the tenant lands at 51. Name the anomaly, say whether PostgreSQL 16 READ COMMITTED or REPEATABLE READ prevents it and why, then give an implementation that holds the cap at READ COMMITTED with the exact statements. Finally, say what changes when the cap is 'at most one running export per tenant' on job_run.

Approach

  1. Name it: write skew. The two transactions read an overlapping set and write disjoint rows, so there is no row-level conflict for the engine to detect and each commit is individually legal.
  2. Rule out the levels precisely. READ COMMITTED takes a fresh snapshot per statement and takes no lock on the counted rows, so both see 49. PostgreSQL's REPEATABLE READ is snapshot isolation: it removes non-repeatable reads and phantoms within the snapshot but still admits write skew, because the anomaly is not a re-read of a changed row, it is a read of a set that a concurrent transaction invalidates. Only SERIALIZABLE closes it, by tracking the read dependency and aborting one transaction with SQLSTATE 40001 — a guarantee that exists only if the application re-runs the whole transaction from the read.
  3. Convert the set predicate into a single-row conflict: keep tenant.active_resource_count and run UPDATE tenant SET active_resource_count = active_resource_count + 1 WHERE tenant_id = $1 AND active_resource_count < 50 in the same transaction as the INSERT. Zero affected rows is the cap, returned as 409. The row lock serialises the decision at any isolation level, and contention is bounded to one tenant's row — which is also the fair-scheduling unit, unlike a global counter that would convoy every tenant behind one row.
  4. State the cost you just took on: a counter is a second source of truth that can drift, so every path that changes status must adjust it inside the same transaction, and a periodic reconciliation has to exist, with resource_revision as the authority for what the count should have been.

Follow-up

  • A resource moves from archived back to active. Which statements change, and what breaks if the counter update and the status change land in different transactions?
  • The cap becomes plan-dependent and a plan can change mid-month. Where does the number 50 live, and who reads it?

Replace offset paging on the resource feed with keyset

Easy · keyset pagination · composite index · sort order

resource holds resource_id, tenant_id, owner_user_id, title, body_ref, version, status ('draft','active','archived','deleted'), created_at, updated_at, deleted_at, with an index on (tenant_id, status, updated_at DESC, resource_id DESC). The listing endpoint returns active resources for one tenant, newest update first, 50 per page, today with LIMIT 50 OFFSET n. Tenants reach page 400 and rows are created while they read. Write the keyset query, define what the cursor carries and how it is encoded, and say which part of the index each predicate uses. Assume PostgreSQL 16.

Approach

  1. Name the two failures separately. OFFSET 20000 makes the server produce and discard 20,000 rows, so page cost grows with depth rather than with page size. Independently, any write that changes how many rows sort above the offset moves the window between two fetches, and the direction decides which anomaly you get: an insert lands at the head of updated_at DESC and pushes already-returned rows down past the boundary, so they are returned a second time; a delete above the offset, or a row whose updated_at is bumped above the cursor, pulls rows up and one is never returned at all. Nothing in the response reveals either.
  2. Write the seek: WHERE tenant_id = $1 AND status = 'active' AND (updated_at, resource_id) < ($2, $3) ORDER BY updated_at DESC, resource_id DESC LIMIT 50. The row-value comparison is one index range rather than a disjunction, and both columns are NOT NULL, which is what makes that comparison well defined.
  3. Map each predicate onto the index: tenant_id and status are equality on the leading columns, (updated_at, resource_id) is the range, and the ORDER BY matches the index order so no Sort node appears and the scan stops after 50 rows. The DESC in the definition only matters for mixed directions — a plain ascending btree on the same columns is read backwards for this query.
  4. Put both sort columns in the cursor and nothing the client can tamper with into another tenant: base64 of (updated_at, resource_id), validated server-side, with tenant_id taken from the principal.

Follow-up

  • The client asks for 'jump to page 400'. What do you offer instead, and what does the honest version cost?
  • Sort order becomes user-selectable across four columns. How many indexes is that, and which would you refuse to add?

How do you optimize the performance of a large-scale React application?

Medium · Technical Proficiency and Framework Expe

Approach

  1. Measure before changing anything: record the slow interaction in the React DevTools Profiler (flamegraph plus 'why did this render') and the Chrome Performance panel, and check field metrics such as LCP, INP and CLS so the fix targets a real bottleneck.
  2. Cut wasted re-renders: colocate state next to the components that use it, split large contexts, wrap expensive leaf components in React.memo and keep their props stable with useMemo/useCallback (an inline object or arrow function defeats memo). Mention that the React Compiler, where a project adopts it, inserts this memoization automatically.
  3. Render less: virtualise long lists and tables with react-window or react-virtuoso so only visible rows mount, and use stable keys (never the array index on reorderable lists).
  4. Ship less JavaScript: route-level code splitting with React.lazy, Suspense and dynamic import(), check the bundle with an analyzer, and remove heavy dependencies that tree shaking cannot drop.
  5. Keep input responsive during heavy updates: useTransition or useDeferredValue for non-urgent renders, debounce expensive filtering, move CPU-heavy work to a Web Worker, and cache server data (React Query or SWR) to avoid request waterfalls.

Follow-up

  • When does React.memo make performance worse rather than better?
  • Typing into a search box lags on a table of several thousand rows. Walk through how you find and fix it.
  • How would you show that your change improved performance for real users, not only on your laptop?

Can you explain the difference between functional components and class components in terms of lifecycle manage

Medium · Technical Proficiency and Framework Expe

Can you explain the difference between functional components and class components in terms of lifecycle management?

Approach

  1. Map the class lifecycle first: constructor, render, componentDidMount, componentDidUpdate(prevProps, prevState, snapshot) and componentWillUnmount, plus shouldComponentUpdate, getDerivedStateFromProps and getSnapshotBeforeUpdate for the rarer cases.
  2. Explain that function components have no lifecycle methods: useEffect(fn, deps) runs after the commit, its cleanup runs before the next run and on unmount, and an empty dependency array gives mount/unmount behaviour. Frame effects as synchronising with dependencies, not as reacting to lifecycle events.
  3. Distinguish useEffect from useLayoutEffect: the layout version runs synchronously after DOM mutation and before paint, so it suits measuring layout to avoid a visible flicker, at the cost of blocking paint.
  4. Cover the closure model: each render captures its own props and state, so a timer or async callback can read stale values, while a class reads this.props at call time. Fix stale values with the functional updater setState(prev => ...) or a ref.
  5. Note the remaining gaps: class setState shallow-merges while useState replaces the value; PureComponent/shouldComponentUpdate map to React.memo; error boundaries still need a class (componentDidCatch or static getDerivedStateFromError) because no hook equivalent exists.

Follow-up

  • Why does an effect run twice in development under StrictMode, and what bug is that surfacing?
  • How would you convert a componentDidUpdate that compares prevProps to trigger a fetch?
  • Why can an error boundary not catch an error thrown inside an event handler or an async callback?

What are the common pitfalls when managing state in complex React applications?

Medium · Technical Proficiency and Framework Expe

Approach

  1. Duplicated and derived state: storing a value that can be computed from props or other state (a filtered list, a total) and syncing it with useEffect causes drift and an extra render. Compute it during render, with useMemo only if profiling shows it is expensive.
  2. Mutating state in place: pushing to an array and calling setState with the same reference makes React bail out, because it compares with Object.is. Use immutable updates (spread, map, filter) or Immer.
  3. Stale values and batching: calling setCount(count + 1) twice in one handler adds one, not two. Use the updater form, and guard async fetches against out-of-order responses with an AbortController or an ignore flag in the effect cleanup.
  4. Context overuse: every consumer re-renders when the provider value changes identity, and an inline object literal as the value changes it on every render. Memoise the value, split contexts by update frequency, or use a store with selectors (Redux useSelector, Zustand) for frequently changing global state.
  5. Mixing server state with client state: hand-rolling loading flags, caching and invalidation for fetched data in a global store. A server-cache library such as React Query or SWR handles that, which leaves the store for real UI state. Also mention index keys on reorderable lists, which attach state to the wrong item.

Follow-up

  • When would you choose the Context API over Redux or another state store?
  • A form re-renders the whole page on every keystroke. How do you find out why and where should that state live?
  • How do you decide whether a piece of state belongs in the URL, local component state or a global store?

Describe your approach to creating reusable, high-performance UI components.

Medium · Technical Proficiency and Framework Expe

Approach

  1. Design the API before the markup: support controlled and uncontrolled use (value/onChange alongside defaultValue), prefer composition with children or compound components over a growing list of boolean props, and pass refs through (forwardRef, or ref as a regular prop from React 19).
  2. Type the props tightly in TypeScript, using discriminated unions so invalid combinations do not compile, and keep logic in a headless hook separate from styling so other teams can restyle without forking.
  3. Build accessibility in from the start: semantic elements, correct ARIA roles and states, full keyboard support and focus management for menus, dialogs and comboboxes.
  4. Make performance a property of the component: stable callback and object props, memoised leaf rendering where profiling justifies it, virtualisation for list-like components, CSS transitions instead of JavaScript-driven animation, and no layout reads interleaved with writes.
  5. Prove and ship it: Storybook stories for every state, React Testing Library tests that query by role, visual regression checks, semantic versioning and tree-shakeable exports with a deprecation path for breaking prop changes.

Follow-up

  • How do you stop a shared component from accumulating a dozen boolean props?
  • A design-system button is causing a whole form to re-render. How do you track that down?
  • How would you make a custom dropdown fully usable from the keyboard and by a screen reader?

How do you design an API contract that balances flexibility with strict typing requirements?

Medium · Full Stack Architecture and Problem Solv

Approach

  1. Start schema-first: describe the REST API in OpenAPI (or GraphQL SDL or protobuf), generate TypeScript types and clients from it, and validate at runtime at the boundary with zod or JSON Schema, since TypeScript types are erased at runtime and cannot check what actually arrives.
  2. Get flexibility through additive evolution: add optional fields, have clients ignore fields they do not know, never change the type or meaning of an existing field, and put breaking changes behind a new version with a deprecation window.
  3. Model variation explicitly instead of loosening types: discriminated unions (a type field with oneOf in OpenAPI) rather than any or untyped maps, and require clients to handle enum values they have not seen.
  4. Give callers flexibility through parameters rather than shape changes: cursor pagination, filters, sparse fieldsets or expand options. State the trade-off if you pick GraphQL instead: flexible queries in exchange for harder HTTP caching and N+1 resolver risk.
  5. Treat errors and retries as part of the contract: one consistent error body (for example RFC 9457 Problem Details), correct status codes and idempotency keys on retried POSTs. Contract tests in CI catch drift between client and server.

Follow-up

  • How would you rename a field without breaking clients you cannot force to upgrade?
  • When would you choose GraphQL or tRPC over REST with OpenAPI?
  • How do you stop the frontend's TypeScript types drifting from what the server actually returns?

How do you ensure your code remains maintainable and scalable as a project grows in complexity?

Medium · Full Stack Architecture and Problem Solv

Approach

  1. Organise by feature or domain rather than by file type, give each module a small public entry point, and enforce boundaries with lint rules (ESLint import restrictions or dependency-cruiser) so the structure survives growth.
  2. Separate concerns: UI components stay presentational, data fetching and business rules live in hooks or services, and pure functions hold the logic so it can be tested without rendering.
  3. Make types carry the contract: TypeScript strict mode, shared types generated from the API schema, and no untyped escape hatches at module boundaries.
  4. Back changes with an automated safety net: unit tests for logic, React Testing Library for components, a few end-to-end flows in Playwright or Cypress, and CI that runs lint, type check and tests on every pull request alongside code review.
  5. Manage change deliberately: record significant decisions in short architecture decision records, refactor incrementally behind feature flags rather than rewriting, keep dependencies current, and avoid premature abstraction (extract on the third real duplicate, not the first).

Follow-up

  • When do you extract a shared abstraction and when do you tolerate duplication?
  • How would you introduce TypeScript strict mode into a large JavaScript codebase without stopping feature work?
  • What signals tell you a part of the codebase has become hard to maintain?

What security best practices do you implement when developing full-stack applications?

Medium · Full Stack Architecture and Problem Solv

Approach

  1. Frame the answer by layer and name concrete threats (the OWASP Top 10 is a good checklist). For XSS: React escapes rendered text by default, so the risks are dangerouslySetInnerHTML (sanitise with DOMPurify), user-controlled URLs in href (allow only http/https schemes) and missing Content-Security-Policy headers.
  2. Authentication and sessions: hash passwords with bcrypt, scrypt or Argon2; keep session tokens in HttpOnly, Secure, SameSite cookies rather than localStorage, which any injected script can read; add CSRF protection for cookie-based auth; use short-lived access tokens with refresh rotation.
  3. Authorise on the server for every request and every object, because client-side checks are only UX. Changing an id in the URL must not expose another user's record (broken object-level authorisation). Validate input with schemas, use parameterised queries against SQL injection, and rate-limit login and expensive endpoints.
  4. Transport and configuration: HTTPS everywhere with HSTS, a CORS allowlist (a wildcard origin cannot be combined with credentials), secrets in a secret manager or server-side environment and never in the frontend bundle, where anything shipped is public.
  5. Supply chain and operations: committed lockfiles, dependency scanning (npm audit, Dependabot), least-privilege cloud IAM roles, and logging that records security events without storing passwords, tokens or unnecessary personal data.

Follow-up

  • Where would you store a JWT in a single-page app, and what are the trade-offs?
  • How would you stop one user from reading another user's records by changing an id in the URL?
  • What does a Content-Security-Policy protect against, and what does it not cover?

One log partition stops advancing while the others drain

Easy · poison message · head-of-line blocking · consumer lag

Search results for a subset of tenants are hours stale; the rest are current. The projection consumer reports lag of zero on 15 of 16 partitions and 400,000 on one. Its error rate is flat and its CPU is idle. outbox_event has no pending rows older than a second, so the relay has published everything it holds. Identify the mechanism, give the ordered checks, and state what you do in the first ten minutes versus what you change permanently.

Approach

  1. Read the lag distribution first. A slow consumer lags everywhere; zero on fifteen partitions and 400,000 on one is not throughput. Idle CPU on the stuck partition means the consumer is not advancing its offset at all, which points at one message it cannot get past rather than at a rate problem.
  2. Exonerate the producer before touching the consumer. No pending outbox rows older than a second means the relay published, so the event exists in the log. This separates never sent from sent and never applied, which are different code paths and usually different owners.
  3. Read the message at the stuck offset and the handler's log lines for its event_id. A flat error rate with no progress has two explanations and you must distinguish them: the handler is throwing and the retry loop is swallowing it, or the handler is blocking on something and never returning. Idle CPU with no error lines favours the second.
  4. Mitigate before diagnosing further. Move the offending event to a dead-letter store and commit the offset past it. Adding consumers does nothing here, because a partition is consumed by exactly one member of the group, and the blast radius is every aggregate hashed to that partition, not only the aggregate that produced the bad event.

Follow-up

  • The dead-lettered event carried aggregate_version 7 and the projection had applied 6. What must the replay do differently if 8 and 9 landed in the meantime?
  • How do you show staleness to the user while the partition is behind, given the API already returns the projection's watermark?

Suggested practice checklist

Each day targets one cluster of the reported questions, moving from React internals through state and async data to API design and security, then the experience stories and a full mock.

Day 1 — Map the loop and audit your stack

  • Write the two reported rounds, Initial Screening and Deep-Dive Technical Rounds, and note what you must be able to show in each.
  • Rate yourself on React, JavaScript or TypeScript, REST APIs, Node.js and Express, CI/CD and AWS or Azure, and mark the two weakest.
  • Outline two or three projects you can walk through, each with the state approach, the API layer and one problem you solved.

Deliverable: A one-page loop map, a rated skills list with the two weakest marked, and project outlines for each of the two or three projects.

Day 2 — React rendering and performance

  • Build a list of several thousand rows, profile re-renders with the React Profiler, then fix them with state moved lower, stable props and windowing.
  • Write a plain-language explanation of virtual DOM reconciliation and keys, and where useMemo and useCallback help or only add cost.
  • Practise answering the large-scale React performance question aloud, ordering your answer as measure, locate, fix, re-measure.

Deliverable: Before-and-after profiler notes for the list, a written reconciliation explanation, and a spoken answer to the performance question.

Day 3 — Components, lifecycle and reuse

  • Convert a class component using componentDidMount, componentDidUpdate and componentWillUnmount into a function component with effects and cleanup.
  • Build a reusable component (a table or select) with typed props, composition through children or render props, and keyboard accessibility.
  • Write your answer on the functional versus class lifecycle question and on how you keep reusable components fast.

Deliverable: The class and hook versions of one component, one typed reusable component, and a written answer to both questions.

Day 4 — State and async data

  • Build a dashboard with nested components and compare lifting state, Context and Redux, noting re-render behaviour for each.
  • Write a fetch hook with AbortController and a loading, success or error union, then wrap it in an error boundary with a retry.
  • List common state pitfalls (derived state stored twice, stale closures, one giant context) and a fix for each, and write down how you would find a memory leak.

Deliverable: A small working dashboard with a fetch hook and error boundary, plus a written list of state pitfalls with fixes.

Day 5 — API contracts, maintainability and security

  • Design a REST contract in TypeScript for one resource, with request and response types, runtime validation, an error format and pagination.
  • Write a maintainability answer covering module boundaries, tests, code review and a CI pipeline.
  • Make a security checklist for a React and Node.js application: XSS, CSRF, token storage, CORS, input validation, dependency updates and secrets handling.

Deliverable: An API contract document, a maintainability answer, and a one-page security checklist.

Day 6 — Experience stories

  • Write the client-plus-server bottleneck story: symptom, how you separated the layers, fix, numbers, and prevention.
  • Write a second story about a problem you found, solved and saw through to deployment, plus one about explaining a trade-off to a product manager or teammate.

Deliverable: Three written stories with real measurements, each rehearsed aloud.

Day 7 — Full dry run

  • Run a mock of both rounds: a short background pitch, then a React build with spoken reasoning, then API and security questions.
  • Answer the React performance question and the bottleneck story back to back without notes.
  • Write two questions to ask the team about how they review code and handle production issues.

Deliverable: A completed mock with notes on weak answers, and two questions for interviewers.

Stories for the performance and ownership questions

Candidates report that the experience-based questions centre on troubleshooting across the client and server, discussing the hardest parts of past projects, and explaining technical trade-offs. The stories that serve this role are ones where you diagnosed a problem, made a decision and saw it through to deployment, with numbers.

  1. Tell me about the most challenging part of a past project, the technical obstacle you faced and how you overcame it.
  2. Describe a time you identified a problem, chose a solution and saw it through to deployment.
  3. Tell me about a time you explained a technical trade-off to a product manager or teammate who disagreed.

How do you handle asynchronous data fetching and error boundaries within a component tree?

Medium · Technical Proficiency and Framework Expe

Approach

  1. Separate the three states first. Model the request as loading, success or error (a discriminated union in TypeScript, not three loose booleans), so the component can never render data and a spinner together.
  2. Fetch safely. In an effect, create an AbortController (or use an ignore flag) and abort in the cleanup function, so a slow response for an old id cannot overwrite a newer one and nothing calls setState after unmount. A library such as React Query or SWR adds caching, request de-duplication and retries if you want them. Say which you would pick and why.
  3. Be precise about what error boundaries catch. A boundary (a class with getDerivedStateFromError to render the fallback and componentDidCatch to log) catches errors thrown while rendering, in lifecycle methods and in constructors of its descendants. It does not catch errors in event handlers, in promises or timers, in server rendering, or in the boundary itself.
  4. Connect the two. Catch the rejected fetch, store the error in state, and either render an inline message (for example a 404 or empty state) or rethrow it during render so the nearest boundary shows the fallback. With Suspense-enabled data sources, a rejected request is thrown to the boundary and the loading state goes to the Suspense fallback.
  5. Place boundaries where failure should be contained: one around each independent widget on a dashboard and one at the route level, each with a reset action (a retry button or a reset key that changes with navigation). Log the error and component stack to monitoring, and retry automatically only for idempotent GET requests with backoff.

Follow-up

  • Why does a try/catch or an error boundary not catch a rejected promise from a fetch inside an effect, and how do you get that error to the boundary?
  • Two filter changes fire quickly and the first response arrives last. How do you stop the stale result from showing?
  • How would you reset a boundary after the user navigates away and back, and what would you log when it triggers?

Describe a time you had to troubleshoot a performance bottleneck that spanned both the client and the server.

Medium · Full Stack Architecture and Problem Solv

Approach

  1. Choose a real incident you personally diagnosed, where the symptom was visible to users (a slow page or a laggy interaction). State it in a sentence or two with the measurement you had: load time, response time or frame drops. Use your own numbers and do not invent any.
  2. Show how you located the layer before changing anything. On the client: the browser performance panel, the React Profiler for re-render counts, bundle size and long tasks. On the wire: the network waterfall, time to first byte versus download time, payload size and request count. On the server: traces or logs per request, and the database query plan for slow queries. Name the tools you actually used.
  3. Describe the cause as a chain across both sides, for example an endpoint doing a query per row feeding a client that fetched several dependent requests in sequence and re-rendered a large list on every response. Say which parts you fixed and in what order, changing one thing at a time and re-measuring after each.
  4. Cover the trade-offs of the fix: cache staleness, a changed API response that other consumers depend on, added complexity from memoisation or batching. Close with the before-and-after numbers and what stops a regression, such as a performance budget, an alert or a test.
  5. Keep the story about your decisions. Say what you ruled out and why, and where you were wrong at first. A story where the team simply fixed it, or where you guessed the cause and shipped, shows neither ownership nor method.

Follow-up

  • How did you establish that the delay was on the server, the network or the client, rather than assuming?
  • What did the fix cost, and who else was affected by the changed API or caching behaviour?
  • What would you put in place now so this class of bottleneck is caught before users see it?

Reverse your own decision and price the reversal

Medium · reversibility · measurement · migrations

Describe a technical decision you made and later reversed. Pick one that cost something: a service you split and merged back, a cache you added and removed, an index you created that pushed the planner onto a worse plan, a projection you rebuilt from scratch. State what you believed when you decided, the measurement that changed your mind, how long the wrong version ran in production, and what the reversal cost in migrations, dual writes, and a deprecation window for callers you did not own.

Approach

  1. State the original rationale without irony, in the version you would still defend given what was known then. If it is not defensible, the story is about carelessness rather than judgement, and a different example serves you better.
  2. Give the measurement that moved with a before and after: the p99 that did not improve, the cache hit rate that sat at 40%, the plan that flipped to a sequential scan once the table passed a size you can name.
  3. Cost the reversal in steps, not adjectives: expand-and-contract deploys, the dual-write window, the callers who had to be notified, the rows already written in the wrong shape that had to be backfilled or abandoned.
  4. Distinguish reversal from rewrite by naming what you kept. Most good reversals preserve the schema or the interface and undo one decision inside it, which is also why they were affordable.

Follow-up

  • What in that decision was irreversible, and did you know it was irreversible when you made it?
  • How did you tell the people who had already built on top of the original decision?