Build a JavaScript Calculator: Complete Guide
Build a working calculator with plain JavaScript, covering number input, operator handling, event delegation, and safe evaluation of the final result.
A JavaScript calculator app takes clicks on number and operator buttons, builds an expression, and shows the result on a display. It is a step up from a counter app because it needs to track more than one piece of state: the first number, the chosen operator, and the second number, before it can calculate anything.
You will build a grid of buttons and a display, wire up every button with a single click listener, and calculate the result without using eval(). Along the way you will use event delegation, a pattern that lets one listener handle clicks from many buttons at once.
What You Will Build
The calculator needs to support:
- Typing digits that appear on the display.
- Choosing an operator like add, subtract, multiply, or divide.
- Calculating and showing the result when equals is pressed.
- Clearing the display to start over.
Every click is one of three kinds, and the app responds differently depending on which button fired the event. The next steps build this decision one piece at a time.
Step 1: Build the HTML
A calculator button grid is a group of buttons inside one shared container.
<div class="calculator">
<input type="text" id="display" readonly />
<div id="buttons">
<button data-value="7">7</button><button data-value="8">8</button>
<button data-value="9">9</button><button data-value="/">/</button>
<button data-value="4">4</button><button data-value="5">5</button>
<button data-value="6">6</button><button data-value="*">*</button>
<button data-value="1">1</button><button data-value="2">2</button>
<button data-value="3">3</button><button data-value="-">-</button>
<button data-value="0">0</button><button data-value="+">+</button>
<button data-value="=">=</button><button data-action="clear">C</button>
</div>
</div>Every button carries its value directly in a data-value attribute, and the clear button uses a separate data-action attribute. Reading the button's own attribute, instead of matching its visible text, keeps the JavaScript from breaking if the label ever changes.
Step 2: Handle Every Button with One Listener
Rather than attaching a click listener to each button, attach one listener to the container and let it figure out which button was clicked.
const display = document.getElementById("display");
const buttons = document.getElementById("buttons");
buttons.addEventListener("click", (event) => {
const button = event.target;
if (button.tagName !== "BUTTON") return;
handleButtonClick(button);
});This technique is called event delegation, covered in more depth in the JavaScript event delegation guide. Clicking any button inside the container fires this one listener, and the guard clause ignores clicks that land on the container itself rather than a button.
Step 3: Track the Calculator State
Before writing the click logic, decide what the app needs to remember between clicks.
let firstNumber = "";
let operator = "";
let secondNumber = "";These three variables hold the state of an in-progress calculation. The display only shows text, so the app cannot rely on reading numbers back out of it reliably once an operator has been chosen.
Step 4: Respond to Number, Operator, and Equals Clicks
With the state variables ready, write the function that decides what each click does.
function handleButtonClick(button) {
const value = button.dataset.value;
if (button.dataset.action === "clear") return resetCalculator();
if (value === "=") return calculateResult();
updateNumbers(value);
}This dispatcher checks the action first, so clear always wins, then checks for equals, then hands everything else to a number and operator handler. Each of the three cases below is its own small function.
function resetCalculator() {
firstNumber = "";
operator = "";
secondNumber = "";
display.value = "";
}Clear wipes every stored value and the display text, so the calculator behaves exactly as if it had just loaded, ready for a brand new calculation with nothing left over from before.
function updateNumbers(value) {
if (!isNaN(value)) {
if (operator) {
secondNumber += value;
} else {
firstNumber += value;
}
} else if (firstNumber && !operator) {
operator = value;
}
display.value = firstNumber + operator + secondNumber;
}A digit appends to the first number until an operator has been chosen, then it appends to the second number instead. An operator is accepted only after the first number exists and only when no operator is already stored. These checks stop an operator from starting an incomplete expression or overwriting the first operator before equals is pressed.
Step 5: Calculate the Result Safely
The temptation with a calculator is to pass the whole display string to eval(). Avoid that, since eval() runs any text as real JavaScript code, which is unsafe the moment the input is not fully trusted.
function getOperationResult(op, first, second) {
if (op === "+") return first + second;
if (op === "-") return first - second;
if (op === "*") return first * second;
return first / second;
}This helper takes two real numbers and the chosen operator, then runs the matching arithmetic directly instead of evaluating arbitrary text.
function calculateResult() {
if (!firstNumber || !operator || !secondNumber) return;
const result = getOperationResult(operator, parseFloat(firstNumber), parseFloat(secondNumber));
display.value = result;
firstNumber = String(result);
operator = "";
secondNumber = "";
}The guard clause ignores equals until both numbers and an operator are present, preventing an incomplete expression from producing NaN. The function then converts the stored strings to real numbers before passing them to the helper above. After showing the result, it resets the operator and second number, but keeps the result as the new first number so the calculator is ready for another operation right away.
Common Mistakes
| Mistake | Why it breaks | Fix |
|---|---|---|
Using eval() on the display text | Runs any text as code, which is unsafe and unpredictable | Convert stored numbers with parseFloat and calculate based on the operator |
| Attaching a separate listener to every button | More code to maintain and misses buttons added later | Use one listener on the container with event delegation |
| Allowing a second operator click to overwrite the first | Produces an incomplete expression on the display | Only accept an operator click when none is already stored |
| Calculating before both numbers are entered | Converts an empty string to NaN | Return early unless both numbers and an operator are present |
Next Step
Event delegation and explicit state variables show up again in bigger UIs, including the JavaScript shopping cart project. If you have not built the JavaScript todo list app yet, it covers rendering a list from an array, a pattern that pairs well with the state tracking used here.
Rune AI
Key Insights
- Use one click listener on the button container instead of one listener per button, known as event delegation.
- Read which button was clicked from the event target, not from a separate listener per button.
- Store the first number, the operator, and the second number in variables instead of parsing the display text.
- Never use eval() to calculate the result; perform the arithmetic directly based on the stored operator.
- Reset the stored state after showing a result so the next calculation starts clean.
Frequently Asked Questions
Why use one click listener on the whole button grid instead of one per button?
Is it safe to use eval() to calculate the result?
Why does the calculator break if I click two operators in a row?
Conclusion
A calculator app looks simple, but it teaches a pattern used in far bigger UIs: track state in variables, read every button through one shared handler, and only touch the display at the end. That combination of event delegation and explicit state is what keeps the logic manageable as the app grows.
More in this topic
Using Reflect and Proxy Together in JavaScript
Proxy traps intercept operations, but Reflect is what makes them behave correctly. Learn why Reflect belongs inside every trap and what breaks when you skip it.
Top JS Array Methods Interview Questions to Know
The array method questions that come up most often in JavaScript interviews, answered directly with short examples: map vs forEach, mutating vs non-mutating methods, reduce, and more.
JavaScript Reflect API: Advanced Architecture
The Reflect object exposes JavaScript's own internal operations as plain functions. Learn every Reflect method, what it returns, and why it exists as its own API.