Execution style: Implement in JavaScript first, then explain the React hook/component structure you would use in production.
Prompt: Build a market-list data transformation layer for a prediction trading UI. I want you to support search, filtering, and sorting while keeping logic readable and testable. Explain your state strategy before coding, including what should be source-of-truth vs derived.
Interviewer-Led Flow (Use In Real Time)
Step 1 — Clarify first: What assumptions do you want to confirm before coding?
Step 2 — State strategy: Walk me through source-of-truth state vs derived state. What do you refuse to store directly, and why?
Step 3 — React mapping: If this were React, where would
useState, useEffect, and useMemo fit? Where would you avoid useMemo as overkill?Step 4 — Challenge questions: What happens at scale? What happens with out-of-order updates? Where can stale state or race conditions appear?
Step 5 — Product and UX: How do accessibility, resilience, and product behavior influence your implementation choices?
Step 6 — Testing: Which parts are pure unit tests vs integration tests? What are your highest-risk test cases?
Step 7 — Production bar: What would you change for production observability, rollbacks, and operational safety?
Users need to quickly find tradable markets during high activity windows.
Starter Dataconst markets = [
{ id: "m1", title: "Will Arsenal win the league?", probability: 0.72, volume: 120000, status: "open", updatedAt: "2026-05-01T10:00:00Z" }
];
Task Requirements
- Search by title (case-insensitive).
- Filter by status/category.
- Sort by probability, volume, time.
- Show empty state and loading/error states.
React concept to use:
Clarifying Questions You Should Ask
useState for controls (search, status, sortBy) and useMemo for derived visibleMarkets. Use this because filter/sort recomputes often; keep markets as source-of-truth and avoid duplicated derived state.Client-side or server-side sort/filter? Dataset size? Real-time updates? Should closed markets remain visible?
Follow-Up Prompts (Interviewer)
- Use
useMemoand explain why. - Explain why duplicated derived state is problematic.
- Add retry and accessibility improvements.
+In-Depth Follow-Up Answers
Why
Filtering and sorting are O(n log n) and run every render by default. In fast-updating UIs, this causes unnecessary CPU work and jank.
useMemo?Filtering and sorting are O(n log n) and run every render by default. In fast-updating UIs, this causes unnecessary CPU work and jank.
useMemo constrains recalculation to dependency changes (markets, search, status, sortBy), improving responsiveness. I’d also mention that useMemo is an optimization, not correctness logic.Why avoid duplicated derived state?
Keeping both
Keeping both
markets and filteredMarkets in state introduces sync bugs, especially with websocket updates. If one updates and the other does not, UI diverges from truth. Deriving visible rows from source + UI controls removes that failure mode and simplifies reasoning and tests.Accessibility improvements:
Use semantic controls (
Use semantic controls (
<label>, <select>, <button>), keyboard-focus styles, aria-live="polite" for async loading/error messages, explicit empty-state copy, and table headers with scope for screen readers.+Full End-to-End Solution
function getSortConfig(sortBy = "updatedAt_desc") {
const map = {
volume_desc: { field: "volume", dir: -1 },
volume_asc: { field: "volume", dir: 1 },
probability_desc: { field: "probability", dir: -1 },
probability_asc: { field: "probability", dir: 1 },
updatedAt_desc: { field: "updatedAt", dir: -1 },
updatedAt_asc: { field: "updatedAt", dir: 1 }
};
return map[sortBy] || map.updatedAt_desc;
}
function getVisibleMarkets(markets, { search = "", status = "all", sortBy = "updatedAt_desc" } = {}) {
const term = search.trim().toLowerCase(); // Normalize search once for consistent matching.
const normalizedStatus = status.trim().toLowerCase();
const sort = getSortConfig(sortBy);
return markets
.filter(m => normalizedStatus === "all" || m.status.toLowerCase() === normalizedStatus) // Filter from source data; do not mutate source markets.
.filter(m => m.title.toLowerCase().includes(term))
.slice()
.sort((a, b) => {
const av = sort.field === "updatedAt" ? new Date(a.updatedAt).getTime() : a[sort.field];
const bv = sort.field === "updatedAt" ? new Date(b.updatedAt).getTime() : b[sort.field];
if (av === bv) return String(a.id).localeCompare(String(b.id)); // Deterministic tie-break prevents unstable list ordering.
return (av - bv) * sort.dir;
});
}
function getMarketListState({ markets, loading, error, filters }) {
if (loading) return { type: "loading", message: "Loading markets..." };
if (error) return { type: "error", message: error, canRetry: true };
const rows = getVisibleMarkets(markets, filters);
if (!rows.length) return { type: "empty", message: "No matching markets found." };
return { type: "ready", data: rows };
}