Blogs / Machine Coding Rounds

Frontend Machine Coding: How to Build a Multi-Step Form

A detailed guide to building a multi-step form for frontend machine coding rounds, covering state management, validation, navigation, and real-world UX patterns.

Mar 20, 202616 min readSevyDevy Team
Frontend Machine CodingMulti Step FormReactFormsValidationUI Engineering

Table of content

  1. 1. Why Multi-Step Form Is Asked in Interviews
  2. 2. What Problem Are We Solving?
  3. 3. Visualizing the User Experience
  4. 4. Core Requirements
  5. 5. Step 1: State Design
  6. 6. Step 2: Handling Input Changes
  7. 7. Step 3: Navigation Logic
  8. 8. Step 4: Step-wise Validation
  9. 9. Step 5: Rendering Steps
  10. 10. Step 6: Complete Component
  11. 11. How the Flow Works
  12. 12. Common Mistakes
  13. 13. Advanced Improvements
  14. 14. Real-World Example
  15. 15. Final Takeaway

Why Multi-Step Form Is Asked in Interviews

Multi-step forms are frequently asked in frontend machine coding rounds because they test structured thinking, state management, validation handling, and user flow design. Unlike simple forms, this problem requires managing data across multiple screens while maintaining a seamless experience.

It also reflects real-world use cases such as onboarding flows, checkout processes, loan applications, and profile creation systems.

What Problem Are We Solving?

Instead of showing a long and overwhelming form, multi-step forms break the input process into smaller steps. Each step collects a subset of data, improving user experience and completion rates.

Visualizing the User Experience

Imagine a signup flow where the user first enters personal details, then address information, and finally account credentials. The user clicks 'Next' to move forward and 'Back' to revisit previous steps. Progress is preserved across steps, and validation ensures correctness before proceeding.

Core Requirements

  • Multiple steps with navigation (Next / Back)
  • Shared form state across steps
  • Validation per step
  • Prevent navigation on invalid input
  • Final submission with all data
  • Optional progress indicator

Step 1: State Design

The key to solving this problem is managing a centralized state that persists across steps. Instead of separate states per step, a single object should hold all form data.

const [step, setStep] = useState(1);

const [formData, setFormData] = useState({
  name: "",
  email: "",
  address: "",
  city: "",
  password: "",
});

This ensures that moving between steps does not reset previously entered data.

Step 2: Handling Input Changes

A generic handler simplifies updates across all fields.

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  const { name, value } = e.target;

  setFormData((prev) => ({
    ...prev,
    [name]: value,
  }));
};

Step 3: Navigation Logic

Navigation between steps should be controlled and predictable.

const nextStep = () => setStep((prev) => prev + 1);
const prevStep = () => setStep((prev) => prev - 1);

Step 4: Step-wise Validation

Each step should validate only its relevant fields before allowing the user to proceed.

const validateStep = () => {
  if (step === 1) {
    return formData.name && formData.email;
  }

  if (step === 2) {
    return formData.address && formData.city;
  }

  if (step === 3) {
    return formData.password.length >= 6;
  }

  return true;
};

Step 5: Rendering Steps

Each step renders only its relevant inputs.

const renderStep = () => {
  switch (step) {
    case 1:
      return (
        <>
          <input name="name" value={formData.name} onChange={handleChange} placeholder="Name" />
          <input name="email" value={formData.email} onChange={handleChange} placeholder="Email" />
        </>
      );

    case 2:
      return (
        <>
          <input name="address" value={formData.address} onChange={handleChange} placeholder="Address" />
          <input name="city" value={formData.city} onChange={handleChange} placeholder="City" />
        </>
      );

    case 3:
      return (
        <>
          <input name="password" type="password" value={formData.password} onChange={handleChange} placeholder="Password" />
        </>
      );

    default:
      return null;
  }
};

Step 6: Complete Component

import React, { useState } from "react";

export default function MultiStepForm() {
  const [step, setStep] = useState(1);

  const [formData, setFormData] = useState({
    name: "",
    email: "",
    address: "",
    city: "",
    password: "",
  });

  const handleChange = (e: any) => {
    const { name, value } = e.target;

    setFormData((prev) => ({
      ...prev,
      [name]: value,
    }));
  };

  const validateStep = () => {
    if (step === 1) return formData.name && formData.email;
    if (step === 2) return formData.address && formData.city;
    if (step === 3) return formData.password.length >= 6;
    return true;
  };

  const nextStep = () => {
    if (!validateStep()) {
      alert("Please fill all fields correctly");
      return;
    }
    setStep((prev) => prev + 1);
  };

  const prevStep = () => setStep((prev) => prev - 1);

  const handleSubmit = () => {
    if (!validateStep()) return;
    console.log("Final Data:", formData);
  };

  const renderStep = () => {
    switch (step) {
      case 1:
        return (
          <>
            <input name="name" value={formData.name} onChange={handleChange} placeholder="Name" />
            <input name="email" value={formData.email} onChange={handleChange} placeholder="Email" />
          </>
        );
      case 2:
        return (
          <>
            <input name="address" value={formData.address} onChange={handleChange} placeholder="Address" />
            <input name="city" value={formData.city} onChange={handleChange} placeholder="City" />
          </>
        );
      case 3:
        return (
          <>
            <input name="password" type="password" value={formData.password} onChange={handleChange} placeholder="Password" />
          </>
        );
      default:
        return null;
    }
  };

  return (
    <div>
      <h2>Step {step}</h2>

      {renderStep()}

      <div>
        {step > 1 && <button onClick={prevStep}>Back</button>}
        {step < 3 && <button onClick={nextStep}>Next</button>}
        {step === 3 && <button onClick={handleSubmit}>Submit</button>}
      </div>
    </div>
  );
}

How the Flow Works

The user fills Step 1 and clicks Next. If validation passes, Step 2 appears. Data persists across steps. The user can go back and edit previous steps. Finally, all data is submitted together.

Common Mistakes

  • Losing data when switching steps
  • Not validating before moving forward
  • Hardcoding step logic instead of scalable design
  • Coupling UI and logic too tightly

Advanced Improvements

  • Add progress bar
  • Persist data in localStorage
  • Use React Hook Form + Zod
  • Add async validation (email check)
  • Add stepper UI (like checkout)

Real-World Example

In an e-commerce checkout, Step 1 collects shipping info, Step 2 collects payment details, and Step 3 confirms the order. This structure improves user completion rate compared to a long single form.

Final Takeaway

Multi-step forms test how well you manage state, validation, and user flow. A strong implementation keeps logic clean, prevents invalid transitions, and ensures smooth navigation.

In machine coding rounds, the goal is not just to make it work but to design it like a production-ready component.

Related blogs