When an interviewer asks you to build a calculator, the first thing that must come to your mind is:

👉 This is a DSA + JavaScript round.


They often expect a scalable evaluation logic, clear trade-offs, and good UX. Before you start, ask these clarifying questions — they’ll tell you what the interviewer really wants:

  1. Does it support brackets ()? (If yes, expect stack-based parsing.)
  2. Are we more focused on CSS / UI or on core functionality?
  3. Can we use eval()? (If yes, focus shifts to UX/CSS; if no, they want parsing/DSA.)
  4. What operations should be supported? (+ - * / only, or %, ^, , etc.?)
  5. Should it handle decimals and negative numbers properly?
  6. Do you need memory functions, history, or clipboard support?
  7. Are keyboard shortcuts / full accessibility required?

Asking these gives you a clear signal of expectations. If they say “yes” to eval(), they usually expect a strong UI demonstration. If they say “no”, roll up your sleeves — you’ll likely need to show parsing, operator precedence, and correctness.

Below is what we are building today

Screenshot 2025-10-02 at 4.54.14 PM.png

Below is a step-by-step, buildable Medium-style post that keeps all your code exactly as you pasted it and explains each part so a reader can implement the component from scratch.

What We're Building

Our end product will be a highly functional Calculator featuring:

  • Custom Expression Evaluator for safe mathematical operations without eval()
  • Reusable Button Component with flexible styling and consistent behavior
  • React Hooks for clean state management and lifecycle handling
  • Keyboard Support with focus management for accessibility
  • Modern Design with professional styling and smooth animations
  • Responsive Design using Tailwind CSS for rapid, utility-first styling
  • Error Handling for edge cases and invalid operations
  • Modular Architecture for easy testing and maintenance

Quick project setup (prerequisites)

  1. Create a React project (Vite or CRA). Example with CRA:
    npx create-react-app my-calculator
    cd my-calculator
  2. Install Tailwind CSS (follow Tailwind docs) or add your preferred styling approach.
  3. Create src/components/Button.jsx and src/components/Calculator.jsx — we'll place code there.

Step 1 — The Reusable Button Component

Create src/components/Button.jsx and paste the exact code below. This component standardizes button styling and layout so the Calculator UI code stays tidy.

import React from 'react';

const Button = ({
children,
onClick,
className = '',
colSpan = 1,
...props
}) => {
const baseClasses = "px-4 py-3 font-semibold text-white rounded-xl shadow-lg transition-all duration-200 hover:shadow-xl active:scale-95";
const spanClasses = colSpan > 1 ? `col-span-${colSpan}` : '';

return (
<button
className={`${baseClasses} ${spanClasses} ${className}`}
onClick={onClick}
{...props}
>
{children}
</button>
);
};

export default Button;


Why this component first?

  • Keeps styling consistent across all buttons.
  • colSpan helps with grid layout (makes the 0 button span two columns).
  • Improves reusability and reduces repetitive code in the Calculator.

Step 2 — Component Structure (Calculator shell)

Create src/components/Calculator.jsx. Start with the basic shell — state, operators, and a ref for focus.

import React, { useEffect, useRef, useState } from "react";

function Calculator() {
const operators = new Set(["*", "+", "-", "/"]);
const [input, setInput] = useState([]);
const inputRef = useRef();

// Component logic here...
}


Why this structure?

  • Set gives O(1) operator checks.
  • input as an array makes token handling straightforward (you append numbers/operators).
  • inputRef lets us manage focus for keyboard support and accessibility.

Step 3 — Number Input Handler

Add the number handler to the component so number button clicks append to the expression.

const numberHandler = (num) => {
setInput((prev) => [...prev, num]);
};


Design choices:

  • Immutable updates ([...]) to avoid mutation bugs.
  • Keep number handling simple — validation and correctness are handled later by the evaluator.

Step 4 — Operator Input Handler

Prevent consecutive operators and make operator input intuitive.

const operatorHandler = (operator) => {
const tempInput = [...input];
if (operators.has(tempInput.at(-1))) {
tempInput.pop();
}
tempInput.push(operator);
setInput(tempInput);
};


Why this matters:

  • Replaces the last operator if user types another one, preventing 5++3.
  • Keeps expression state clean and predictable.

Step 5 — The Custom Expression Evaluator (the heart)

Implement a safe evaluator that respects operator precedence (handles *// before +/-) without using eval().

function evaluateExpression() {
let currentNum = "";
let currentOp = "+";
const operationArray = [];
let index = 0;

const performOperation = (operator, num) => {
if (operator === "+") {
operationArray.push(+num);
} else if (operator === "-") {
operationArray.push(-num);
} else if (operator === "/") {
const first = operationArray.pop();
operationArray.push(first / num);
} else if (operator === "*") {
const first = operationArray.pop();
operationArray.push(first * num);
}
};

while (index < input.length) {
if (operators.has(input[index])) {
performOperation(currentOp, currentNum);
currentNum = "";
currentOp = input[index];
} else if (!isNaN(input[index])) {
currentNum += input[index];
}
index += 1;
}

performOperation(currentOp, currentNum);
return operationArray.reduce((current, num) => current + num, 0);
}


Why this evaluator works:

  • Handles multiplication/division before addition/subtraction by pushing intermediate results and using pop() for higher precedence ops.
  • No eval() → safe and testable.
  • Extensible: add more operators by extending performOperation.

Step 6 — Evaluation Handler (connect evaluator to UI)

Hook the evaluator up to UI with a handler that replaces the input with the result.

const evaluateHandler = () => {
const result = evaluateExpression();
setInput([result]);
};


Note: In production, wrap this in try/catch and validate isFinite / isNaN (we’ll show that in Advanced Features).

Step 7 — Focus Management (Accessibility)

Focus the input on mount so keyboard users can start typing immediately.

useEffect(() => {
inputRef.current.focus();
}, []);


Accessibility wins:

  • Keyboard-first UX.
  • Screen readers will pick up the focus and current display.
  • Small detail that makes a big difference.

Get the complete code from here