The Question
FE DesignDesign a Scalable Polling and Survey Platform Frontend
Design the frontend architecture of a highly scalable, offline-resilient survey and polling platform similar to Typeform or Slido. The system must support two key user experiences: a highly interactive Survey Runner engine that dynamically executes complex skip logic (branching) on the client, and a Live Poll Presentation view that visualizes real-time voting trends under extreme concurrency. Detail your component design, dynamic loading strategy for field inputs, offline queue persistence mechanism, rendering performance optimizations for complex inputs, and accessibility standards to support high-traffic events under weak network conditions.
React
Tailwind CSS
Zustand
IndexedDB
Server-Sent Events
TypeScript
Questions & Insights
Clarifying Questions
Scale and Audience: What is the scale of the target audience, and what are the concurrency requirements for survey submissions and live polling views?
Assumption: The runner must handle up to millions of passive respondents with spikes of 50,000+ concurrent requests during live-broadcast polling events.
Scope of Platform: Are we designing both the Survey Creator (Builder) interface and the Survey Respondent (Runner) interface?
Assumption:* To adhere to the YAGNI principle for an MVP, we will prioritize the high-traffic Survey Runner and Live Poll Viewer engines, with a minimal, schema-driven Survey Creator UI**.
Network & Device Profile: What are the target device profiles and network conditions of the respondents?
Assumption: Primarily mobile web (80%+ traffic). The runner must load sub-1.5 seconds on a slow 3G connection and operate reliably offline or under intermittent connectivity.
Complex Branching Logic: Do surveys support complex dynamic paths, skip logic, and localized validations?
Assumption: Yes. The client-side application must evaluate conditional branching trees (e.g., “If Q1 == 'Yes' AND Q2 > 5, skip to Q5”) at runtime without requiring roundtrips to the server.
---
Crash Strategy
Our architecture strategy focuses on client-side performance, local reliability, and extreme scalability. We solve the core bottlenecks of dynamic form rendering, dynamic branch evaluation, and high-concurrency ingestion through the following progressive execution steps:
[Dynamic Schema Parser] ──> [Client-Side Logic Engine] ──> [Transactional Response Queue] ──> [Real-time Live View]
Progressive Questions for Architecture Flow:
How do we render massive, dynamically generated forms without suffering browser main-thread degradation? We use a windowed, single-question-at-a-time pagination model backed by dynamic, lazily loaded dynamic sub-inputs.
How do we execute branching logic instantly? We run a lightweight client-side AST (Abstract Syntax Tree) expression evaluator over the flat response state vector.
How do we ensure zero data loss on spotty mobile connections? We construct an offline-first transactional queue using IndexedDB with optimistic UI synchronization.
How do we prevent live polling result visualization from melting the DOM? We separate state aggregation from rendering, utilizing WebSockets/SSE with data throttling and canvas/WebGL charts.
---
Elite Bonus Points
AST-Based Skip Logic Engine: A highly optimized, dependency-free logical expression parser that translates rules into pre-compiled Javascript functions, executing complex dynamic branching in < 1\text{ms}.
Zero-Bundle-Bloat Dynamic Fields: Implement dynamic loading of question types (
React.lazy over dynamic chunk imports). The core bundle remains tiny (~30KB), loading specialized inputs (e.g., Matrix Grid, Rich Media Upload, Signature Pad) only when defined in the incoming schema.Debounced Offline Sync Queue: Background synchronization layer using the browser’s
BackgroundSync API to guarantee survey submissions succeed even if the tab is closed immediately after hitting "Submit".Semantic DOM Focus Management: Enforcing automatic, programmatic focus traps and keyboard routing when navigating between wizard steps, strictly adhering to WCAG 2.1 AA accessibility guidelines.
---
Design Breakdown
Design Summary
Concise Summary
A decoupled, schema-driven architecture that serves static, highly cacheable survey structures via global CDNs, parsing schemas on the client-side using a high-performance rendering shell. This shell leverages an offline-first transactional pipeline to guarantee fast, zero-data-loss responses on flaky mobile networks.
Major Components
Survey Runner Page (`SurveyRunner`): The orchestrating UI wrapper that maintains layout, routes, and step navigation.
Dynamic Form Renderer Component (`DynamicFormRenderer`): Resolves and mounts the exact subset of lazily loaded field inputs required for the active screen view.
Logic Engine Service (`LogicEngine`): Evaluates JSON-defined predicate trees to compute the list of visible questions and branch paths dynamically.
Response State Store (`ResponseStore`): An atomic, client-side store maintaining the survey's active state, keeping visual renders isolated from background calculations.
Sync Manager Client (`SyncManager`): Manages reliability via an offline-first transaction log, persisting unsaved responses to IndexedDB and synchronizing with the backend in the background.
CUJ Walkthrough
Runner Load: The respondent navigates to the survey URL. The shell fetches the tiny survey layout schema from the CDN cache.
Dynamic Rendering & Branching: The
SurveyRunner reads the schema. The LogicEngine inspects the initial state and filters visible fields. DynamicFormRenderer imports and displays the first step's inputs.Answering & Auto-Save: As inputs change, the answers are dispatched to the
ResponseStore. The state is mirrored instantly to IndexedDBStore.Validation & Next Step: When clicking "Next", the system validates inputs. The
LogicEngine computes the next target step based on answers, bypassing skipped questions.Submission: Upon clicking "Submit", the
ResponseStore marks the session complete. The SyncManager pushes the response payload to the API client. If the network drops, it remains in the queue, retrying via background sync.Simplicity Audit
This architecture utilizes a flat JSON configuration schema to bypass heavy serverside template rendering. Instead of complex, variable multi-page server architectures, the runner acts as a generic interpreter. This allows the builder to output simple JSON configurations, minimizing the surface area of the backend to simple, high-speed static CDN hosting and a write-only analytics ingestion API.
Architecture Decision Rationale
Why this architecture is the best for this problem: Separating the logical evaluation rules from the rendering components isolates complex form rendering cycles. This minimizes React re-renders to only the input fields being updated, ensuring continuous smooth interactions even on low-end mobile devices.
Requirement Satisfaction:
Lighthouse/FCP: Achieved by dynamic chunk splitting and small initial client bundle size.
Reliability: Solved by local storage transactional queues.
Accessibility: Native elements are wrapped in clean semantic boundaries to guarantee assistive device traversal.
---
System Diagram
---
Architecture Deep Dive
Presentation Layer
Component Hierarchy
The design follows a strict hierarchical decoupling pattern to prevent layout thrashing and maintain high atomic update performance:
Outter App Shell: The root context supplying global telemetry, error boundaries, theme providers, and configuration scopes.
Survey Layout: Grid structure rendering the persistent header, footer, skip-to-content links, and navigation boundaries.
Survey Runner Page: The top-level routed view. Parses URL route parameters to load specific surveys and coordinates data hydrations.
Survey Feature Container: Keeps local state coordinates. Connects the rendering UI to the underlying Application and Domain stores.
Dynamic Form Renderer Component: Decoupled container checking the active question list. Instead of hardcoding fields, it maps custom field IDs to their dynamically imported equivalents:
const InputRegistry: Record<string, React.LazyExoticComponent<any>> = {
text: React.lazy(() => import('./fields/TextField')),
mcq: React.lazy(() => import('./fields/MCQField')),
matrix: React.lazy(() => import('./fields/MatrixField')),
};Leaf Components (Progress Bar, Inputs): Highly styled, accessible, stateless components receiving strictly bounded primitive props to guarantee fast render cycles.
Interaction Layer
A11y (Accessibility): Standard native components wrapped inside custom styled interfaces (e.g., hiding a native checkbox offscreen but using its native focus triggers). Focus is systematically moved using ref references upon screen transitions:
const handleNextStep = () => {
// Moves focus to the first interactive field of the next step
requestAnimationFrame(() => {
firstInputRef.current?.focus();
});
};Inputs Validation: Input elements implement immediate change validation. To prevent cognitive fatigue, validation errors are evaluated on field blur, while errors are cleared dynamically on input change.
Animations: Clean, lightweight CSS-only transitions (
transform, opacity) are triggered between steps. No heavy Javascript animation frameworks (e.g., Framer Motion) are used to keep bundle sizes minimal.Rendering Layer
SSR vs. Hydration Strategy: The wrapper shell is static. For public surveys, the initial outer template is SSR-rendered at the Edge to deliver immediate raw HTML. However, respondent interaction and the dynamic logic engine require immediate hydration.
Reconciliation Optimization: Every input is memoized (
React.memo). Global store mutations are decoupled using a scoped state pattern (using a selector-based store) so that a respondent typing in a text field does not trigger re-renders on the parent progress indicator or other distant fields.Handling Heavy UI: In massive matrix grids, we implement virtualization (e.g., rendering only visible rows if a matrix question contains dozens of options) to maintain standard DOM nodes at a manageable level.
UI Frameworks / Tools
Framework: React 18+ (with concurrent features enabled for transition prioritization).
CSS Management: Tailwind CSS (utility first, compilation optimization, zero runtime JS execution overhead).
A11y Helper Libraries: Radix UI primitives (e.g., Slider, FocusScope) are imported modularly where complex accessibility patterns (like custom range sliders) are required.
---
Application Layer
Data Fetching Layer
Dynamic CDN Caching: Survey schemas are highly static documents. The runtime fetches the schema JSON via a cache-first CDN strategy (
stale-while-revalidate).Response Ingestion Pipeline: Responses are packaged into highly compressed JSON payloads. We chunk transmission values of large surveys to avoid single massive submissions on completion:
interface AnswerPayload {
surveyId: string;
respondentId: string;
responses: Array<{ questionId: string; val: any; timestamp: number }>;
}Request Batches & Retries: Retries use an exponential backoff jitter strategy (2^n + \text{random}). Under total network failure, the application transfers tracking context to the local client queue.
State Management Layer
Selector-Based Atomic State: The platform uses a store model (e.g., Zustand) providing direct selector bindings. Renders are strictly subscribed to the specific field paths they display:
// Component only re-renders when this specific field changes const answer = useResponseStore(state => state.answers[questionId]);
Sync Strategy: Response data is synchronized across tabs using a simple
BroadcastChannel mechanism if a user opens the same survey on multiple windows, matching changes to the nearest timestamp indicator.Routing & Navigation
Linear & Non-Linear Routing: Supports URL-based wizard paths (e.g.,
/survey/:id/step/:stepIndex). If skip logic bypasses steps, the route path intercepts navigation commands, running the LogicEngine to update URL structures programmatically via the SPA Router.---
Domain Layer
Business Rules / Use Cases
Logic Validation Rules: Pure functional validation library running validation predicates (e.g., regex checks, range boundaries, date intervals) isolated entirely from React components.
Logic Evaluation Engine: The logical engine evaluates the survey’s current branching state. Below is an implementation of a client-side condition evaluator using declarative logic rules:
type Operator = 'EQUALS' | 'GREATER_THAN' | 'CONTAINS';
interface Condition {
field: string;
operator: Operator;
value: any;
}
interface BranchRule {
conditions: Condition[];
logicalOperator: 'AND' | 'OR';
action: {
type: 'SHOW' | 'HIDE' | 'SKIP_TO';
target: string;
};
}
export class LogicEngine {
static evaluateCondition(cond: Condition, state: Record<string, any>): boolean {
const currentValue = state[cond.field];
if (currentValue === undefined) return false;
switch (cond.operator) {
case 'EQUALS':
return currentValue === cond.value;
case 'GREATER_THAN':
return Number(currentValue) > Number(cond.value);
case 'CONTAINS':
return Array.isArray(currentValue) && currentValue.includes(cond.value);
default:
return false;
}
}
static evaluateRule(rule: BranchRule, state: Record<string, any>): boolean {
const results = rule.conditions.map(c => this.evaluateCondition(c, state));
return rule.logicalOperator === 'AND'
? results.every(Boolean)
: results.some(Boolean);
}
}Entities / Models
Survey Schema Entity: Structurally immutable type mapping.
Validation Engine Contract: Enforces validation integrity.
API DTO mapping: Decouples server payload structures (e.g., nested relational schemas) into normalized flat client maps (indexed by
questionId hashes) for processing performance.---
Infrastructure Layer
API / Network
Inbound Data Ingestion: Pure RESTful API with lightweight payloads optimized for fast CDN distribution.
Outbound Real-Time Event Stream: Live presentation views utilize server-sent events (SSE) for one-way streams (results to viewers), which is simpler and more firewall-friendly than WebSockets, while dynamically falling back to long polling.
Request Compression: Support for gzip/brotli compression on both outgoing response bodies and incoming schemas to guarantee quick network transfers.
[Survey Runner App] ──(SSE Connection)──> [Live Ingest Platform]
│
(JSON POST Queue)
▼
[Reliable Sync Gateway]Storage
IndexedDB Sync Protocol: Operates as an offline transactional log. All changes are written to IndexedDB synchronously inside a low-latency microtask.
Retention & Security: Survey responses stored locally are cleared immediately upon successful HTTP upload confirmation to prevent shared-device exposure.
---
Wrap Up
Wrap-up
Evaluation & Performance Trade-offs: Storing response history in a local-first architecture uses more client-side memory, but completely prevents the user from losing work.
Optimizations under stress: In the event of a live poll with hundreds of thousands of parallel users, WebSockets/SSE updates are aggregated at the server level and throttled to maximum updates of once per 300\text{ms} on the frontend to avoid rendering lockups.
Edge Cases: If a user modifies their local clock while taking a time-constrained survey, validation checks utilize cryptographic network timestamps generated at the initial fetch phase combined with high-precision relative window performance timers (
performance.now()).---