“Build a form builder” or “build a dynamic forms engine” is a senior+ frontend interview that probes whether you can design a system that renders forms from a declarative spec. Used by Typeform, Google Forms, JotForm, Survey-style builders, and admin tools that need user-defined forms. The interview tests state management, validation, conditional logic, and the patterns that scale to hundreds of fields.
Clarify scope
- Form rendering only or full builder (drag-drop UI to compose forms)?
- What field types? (text, select, multi-select, date, file, signature)
- Conditional / branching logic?
- Multi-step / wizard?
- Save partial responses?
The schema
{
id: "feedback-form",
fields: [
{ id: "name", type: "text", label: "Your name", required: true },
{ id: "rating", type: "radio", label: "Rating",
options: [
{ value: "1", label: "1" }, { value: "2", label: "2" },
... { value: "5", label: "5" }
]},
{ id: "comments", type: "textarea", label: "Comments",
condition: { field: "rating", op: "lessThan", value: "4" } }
]
}
JSON Schema is a standard alternative; many tools use it as the spec.
Rendering engine
- Iterate fields; render each based on type
- Type registry:
{ text: TextField, select: SelectField, ... } - Easy to extend with new field types
- Each field reads/writes from a single form state object
State management
- Use react-hook-form, Formik, or roll your own
- react-hook-form is the modern default — uncontrolled, fast, small
- State shape:
Map<fieldId, value> - Validation runs on change / blur / submit (configurable)
Validation
- Per-field rules: required, minLength, pattern, custom function
- Cross-field rules: “passwords match”, “end date after start date”
- Library: Zod, Yup, Valibot — define schema, validate against it
- Async validation (e.g., username availability) with debouncing
- Error display below each field; summary at top for accessibility
Conditional fields
Show/hide fields based on other field values:
- Each field has an optional
condition - Renderer evaluates condition against current state
- Hidden fields: do not collect data; do not validate
- Animation when fields appear / disappear
Multi-step / wizard
- Group fields into steps
- Validate current step before advancing
- Progress indicator
- Allow back-navigation; preserve state
- Optional: save partial response on each step (resume later)
File upload
- Drag-and-drop zone or click-to-select
- Multi-file with previews
- Validation: type, size
- Direct-to-S3 upload with signed URL
- Show progress; allow retry / cancel
The “form builder” UI (if in scope)
- Drag fields from a palette into the form
- Click a field to edit its config (label, options, validation)
- Preview tab shows the rendered form
- Save outputs the JSON schema
- Versioning: keep old versions for forms that have responses
Submission
- Validate full form on submit
- Show inline errors for each invalid field
- Loading state during submission
- Success: redirect or inline confirmation
- Failure: clear retry path; do not lose data
Accessibility
- Each field has a visible
<label>associated viahtmlFor - Required fields marked with aria-required and visible asterisk
- Errors linked via
aria-describedbyandaria-invalid - Validation summary at top, focused on submit failure (jumps screen-reader user to errors)
- Keyboard: tab through in order; submit on Enter from text inputs (not textarea)
Save and resume
- Persist current values to localStorage on each change (debounced)
- Restore on mount
- For logged-in users: server-side save with periodic sync
- “Resume where you left off” UX
Performance considerations
- For 100+ field forms, virtualize the field list if multi-step is not used
- Memoize field components by ID
- Avoid re-rendering whole form on every keystroke (uncontrolled inputs help)
- Defer validation that requires server calls (debounce)
Edge cases interviewers love
- Cyclic conditional logic (A depends on B depends on A) — detect and surface
- User edits the schema while a form has in-progress responses — versioning
- Network drops mid-submission — queue and retry, do not lose data
- File upload completes but submission fails — keep file reference, retry submit
- Partial state from localStorage corrupted — graceful reset
What separates senior from staff
Senior implementations build a static form. Staff implementations handle the schema-driven rendering, conditional fields, multi-step, and persistence. Principal candidates discuss the form-versioning story, the integration with backend schemas (Zod, JSON Schema), and the AI-assisted form-builder where users describe a form in natural language.
Frequently Asked Questions
Library options for the rendering layer?
react-hook-form + zod is the modern stack. Tanstack Form is newer and growing. Formik is legacy.
Should I store form definitions in code or DB?
Static forms (signup, settings): code. User-defined forms (Typeform, Google Forms): DB with JSON schema.
What about field libraries with built-in components?
For interview, build the field types yourself. For production, react-jsonschema-form or @rjsf/core renders forms from JSON Schema with reasonable defaults.