# Build a Form Builder / Dynamic Forms Engine

Source: https://www.techinterview.org/post/3233475412/build-form-builder-dynamic-forms-engine/
Updated: 2026-05-06 · techinterview.org

"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 via `htmlFor`

- Required fields marked with aria-required and visible asterisk

- Errors linked via `aria-describedby` and `aria-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.
