Making a Progress Bar in Machine Coding Rounds

Yash GroverYash Grover
5 min read

Progress bars are one of the most common UI elements used to indicate the progress of a task β€” whether it’s loading, uploading, or downloading. In frontend interviews and machine coding rounds, building a progress bar is a great way to showcase your understanding of React, state management, and user interaction.

Let’s walk through how to create a simple, smooth, and customizable progress bar in React.

( You can find the detailed code and working codesandbox example here ).


✨ Building the Base Progress Bar

We’ll start with a basic version of the progress bar which increments automatically over time.

App.js

import { useEffect, useState } from "react";
import ProgressBar from "./components/ProgressBar";
import "./styles.css";

export default function App() {
  const [value, setValue] = useState(0); // Tracks the current progress value
  const [isCompleted, setIsCompleted] = useState(false); // Tracks whether the progress bar has completed

  useEffect(() => {
    // Set up an interval to increase the value every 100 milliseconds
    const interval = setInterval(() => {
      setValue((val) => val + 1);
    }, 100);

    // Clean up the interval when the component is unmounted 
    return () => clearInterval(interval);
  }, []);

  return (
    <div className="App">
      <h1 style={{ textAlign: "center" }}>Progress Bar</h1>
      <ProgressBar
        value={value}
        onComplete={() => {
          setIsCompleted(true); // Set completion state when value reaches 100
        }}
      />
      {isCompleted && <div>Completed</div>} {/* Conditionally render completion message */}
    </div>
  );
}

What This Code Does

  • We use useState to maintain the progress value and track completion.

  • useEffect is used to simulate an auto-incrementing timer.

  • The ProgressBar component is passed the current value and a callback to be triggered on completion.


ProgressBar.js

import { useEffect, useState } from "react";
import "./ProgressBar.css";

const ProgressBar = ({ value, onComplete }) => {
  const [percentage, setPercentage] = useState(value);

  useEffect(() => {
    const limitedValue = Math.min(100, Math.max(0, value)); // Clamp value between 0 and 100
    setPercentage(limitedValue);

    if (limitedValue >= 100) {
      onComplete(); // Trigger callback if progress reaches 100
    }
  }, [value]);

  return (
    <div className="progress">
      <span style={percentage > 49 ? { color: "white" } : { color: "black" }}>
        {percentage.toFixed()}%
      </span>
      <div className="solidProgress" style={{ width: `${percentage}%` }} />
    </div>
  );
};

export default ProgressBar;

Explanation

  • We clamp the incoming value between 0 and 100 to keep the bar safe.

  • useEffect ensures the component updates smoothly as value changes.

  • When the value hits 100, we notify the parent component using onComplete.

  • The percentage is visually displayed and styled based on its value.


ProgressBar.css

.progress {
  width: 100%;
  border-radius: 20px;
  background: #e3e3e3;
  overflow: hidden;
  border: 1px solid black;
  height: 20px;
  position: relative;
}

.progress span {
  position: absolute;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
}

.solidProgress {
  background-color: green;
  height: 100%;
  transition: width 0.2s ease;
}

Explanation

  • .progress styles the container with a rounded background.

  • The span element overlays text at the center.

  • .solidProgress animates the growing green bar.


πŸ”„ Follow-Up Questions You Might Get (With Solutions)

1. Pause and Resume

Question: Can you pause and resume the progress?

Solution:

const [isPaused, setIsPaused] = useState(false);
const intervalRef = useRef(null);

useEffect(() => {
  if (!isPaused && value < 100) {
    intervalRef.current = setInterval(() => {
      setValue((val) => val + 1);
    }, 100);
  }
  return () => clearInterval(intervalRef.current);
}, [isPaused]);

<button onClick={() => setIsPaused(true)}>Pause</button>
<button onClick={() => setIsPaused(false)}>Resume</button>

2. Optimize Performance

Question: How would you avoid unnecessary renders?

Solution:

const ProgressBar = React.memo(({ value, onComplete }) => {
  const percentage = Math.min(100, Math.max(0, value));

  useEffect(() => {
    if (percentage >= 100) onComplete();
  }, [percentage]);

  return (
    <div className="progress">
      <span>{percentage}%</span>
      <div className="solidProgress" style={{ width: `${percentage}%` }} />
    </div>
  );
});
  • We use React.memo to prevent re-renders unless props change.

  • State updates are limited only when value differs.

3. Add Smooth Transitions

Question: Can the animation be smoother?

Solution: Add a transition to .solidProgress:

.solidProgress {
  transition: width 0.3s ease-in-out;
}

4. Circular Progress Bar

Question: How would you show progress as a circle?

Solution:

<svg width="120" height="120">
  <circle r="50" cx="60" cy="60" stroke="lightgray" fill="none" strokeWidth="10" />
  <circle
    r="50" cx="60" cy="60"
    stroke="green" fill="none" strokeWidth="10"
    strokeDasharray={2 * Math.PI * 50}
    strokeDashoffset={(1 - percentage / 100) * 2 * Math.PI * 50}
    style={{ transition: "stroke-dashoffset 0.3s" }}
  />
</svg>

5. Add Tests

Question: How would you test the progress bar?

Solution:
A basic test would seem like :

it("should call onComplete when value is 100", () => {
  const mock = jest.fn();
  render(<ProgressBar value={100} onComplete={mock} />);
  expect(mock).toHaveBeenCalled();
});

πŸ—‚οΈ File Upload + Progress Bar Example

Through the example below, we will observe how a progress bar can be synced with a file upload process.
We will be making a mock request to "https://httpbin.org/post".

export default function App() {
  const [progress, setProgress] = useState(0);
  const [uploading, setUploading] = useState(false);

  const handleFileUpload = (event) => {
    const file = event.target.files[0];
    if (!file) return;

    const formData = new FormData();
    formData.append("file", file);

    setUploading(true);
    setProgress(0);

    const xhr = new XMLHttpRequest();
    xhr.open("POST", "https://httpbin.org/post");

    xhr.upload.onprogress = (event) => {
      if (event.lengthComputable) {
        const percent = (event.loaded / event.total) * 100;
        setProgress(percent);
      }
    };

    xhr.onload = () => setUploading(false);
    xhr.onerror = () => setUploading(false);

    xhr.send(formData);
  };

  return (
    <div className="App">
      <h1>Upload a File</h1>
      <input type="file" onChange={handleFileUpload} />
      {uploading && <ProgressBar value={progress} />}
      {!uploading && progress >= 100 && <div>Upload Completed!</div>}
    </div>
  );
}

Explanation

  • We capture a file using <input type="file" />

  • Upload is done using XMLHttpRequest, which gives us progress events.

  • The progress bar is updated in real-time as the file uploads.


πŸ“† Conclusion

A progress bar may seem like a small UI element, but it opens up a world of things to test in machine coding rounds β€” state management, animation, cleanup, component design, performance, and real-world integrations like file uploads.

Practice building and extending it β€” maybe even try adding drag-and-drop upload or error handling next. You’ll not only ace this question but become better at thinking in reusable, modular UI patterns.

Happy coding! πŸš€ - notyashgrover.in

0
Subscribe to my newsletter

Read articles from Yash Grover directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Yash Grover
Yash Grover