Good morning! Here's our prompt for today.
Let's build a very basic calculator by programming it! You've probably seen one that look similar to this:

But we won't be building a physical calculator (HardwareDaily will need to wait!)-- instead, we'll be writing a simple program that can serve as one. Can you write a function that will evaluate a simple expression string without using your language's eval
method?
1const expr = "2 + 2";
2calculator(expr)
3// 4
It should also be able to handle positive and negative symbols +,-
, parentheses ()
, and spaces.
1let expr = "3 - 1 + 2";
2calculator(expr)
3// 4
4
5expr = "(2+(4+2)-1)-(5+8)"
6calculator(expr)
7// -6
Assume for now that you don't need to handle multiplication or division cases.
Constraints
- Length of the given string <=
100000
- The numbers present in the string will be a single digit number
- Expected time complexity :
O(n)
- Expected space complexity :
O(n)
Try to solve this here or in Interactive Mode.
How do I practice this challenge?
xxxxxxxxxx
var assert = require('assert');
​
function calculator(str) {
// implement this method
return str;
}
​
try {
assert.equal(calculator('2 + 2'), 4);
​
console.log('PASSED: ' + "assert.equal(calculator('2 + 2'), 4)");
} catch (err) {
console.log(err);
}
​
try {
assert.equal(calculator('(2+2) - 3'), 1);
​
console.log('PASSED: ' + "assert.equal(calculator('(2+2) - 3'), 1)");
} catch (err) {
console.log(err);
}
​
Here's our guided, illustrated walk-through.
How do I use this guide?
Setting the Stage: The Art of Mathematical Evaluation
Calculators, those nifty little devices that have saved us during countless math classes. But what if we could replicate that magic in software?
The idea is to build a "software calculator" that can evaluate mathematical expressions. Our goal is to evaluate expressions like 2 + 2
and (2+(4+2)-1)-(5+8)
without using the built-in eval
function in any language.
Why Skip the eval
Function?
You might wonder, "If there's a built-in way to evaluate code strings, why not just use it?" Well, that's a great question! The eval
function is quite handy, but it has its drawbacks:
- Security Risks: Using
eval
can expose your code to security vulnerabilities. - Limited Customization: What if you want to extend the calculator functionality? With
eval
, you're stuck with what the language offers. - Learning Opportunity: Building our own calculator is an excellent exercise for understanding parsing and evaluation algorithms.
How eval
Works, Anyway?
Just to give you an idea, if we were to use eval
, you could evaluate an expression like this:
1console.log(eval("1+1"));
Thinking Like a Compiler
We can't use the built-in eval
method, but that's not going to stop us.

Let's put on our compiler hats and dissect this problem. Essentially, our job is to translate the language of mathematics into something the machine understands. Our plan involves two critical steps:
Step 1: Parsing the Expression
In this step, we're essentially acting as translators. We must walk through each character in the expression string and categorize it. Think of it as scanning a sentence and tagging each word as a noun, verb, or adjective. In our case, we have four types of "words" or symbols:
- Integers: These are the numbers in our expression. We'll detect them and store their values for computation.
- Signs/Operations: We're only concerned with addition and subtraction, represented by
+
and-
. - Parentheses: These
(
and)
symbols affect the order of operations. - Spaces: They can be ignored for our purpose but need to be identified nonetheless.
Step 2: Evaluating the Expression
Once we know what each symbol stands for, we can proceed to do the actual math. This is the part where we apply the rules of arithmetic to get our final answer.
Crafting a Strategy
Our roadmap seems pretty straightforward; however, there are some "watch out!" signs along the way. The devil is in the details—or in this case, the order and combination of these symbols.
A Simple Example: '1+1'
Let's use the simple expression 1+1
to illustrate our approach. Imagine a variable result
that stores the outcome. Now, our logic could look like this:
- Initialize
result
to 0. - Loop through each character in the string '1+1'.
- Identify the character type:
- If it's a number, add it to
result
. If it's a
+
, continue adding toresult
.
Our logic flows naturally, but as we'll discover, more complex expressions will require a more nuanced approach.
1const expression = "1+1";
2let result = 0;
3
4for (const char of expression) {
5 if (!isNaN(char)) {
6 result += Number(char);
7 }
8 // If it's a '+', we simply continue the loop
9}
10console.log("Result:", result);
The Elegance of Addition
Addition is straightforward, isn't it? When we encounter a number in our expression string, we simply add it to our running total, which we'll call result
. That's about it for addition.
Subtraction as "Negative Addition"
Here's where the subtlety comes in. Instead of thinking of subtraction as a separate operation, let's reframe it as adding a negative number. This concept simplifies our logic beautifully. Now, we don't have to juggle between two different operations; it's all addition!
A Practical Example: '1-1'
To illustrate how this works, let's consider the expression 1-1
. Our logic could flow as follows:
- Initialize
result
to 0. - Loop through each character in the string '1-1'.
- Identify what each character is:
- If it's a number, add it to
result
. - If it's a
-
sign, prepare to make the next number negative.
By following these steps, we'll add 1 to result
, and then effectively add -1 to result
, resulting in a final value of 0.
In the above example, we simply converted the signage when we encountered a minus symbol-- so we need to keep some concept of what sign
we're at. This will allow us to keep track of what operation we're performing, whether it's +
or -
.
Assuming we handle signage with a 1
or a -1
, our method so far starts to look something like this:
xxxxxxxxxx
function calculator(str) {
let result = 0, sign = 1;
for (let i = 0; i < str.length; i++) {
const curr = str.charAt(i);
if (curr === '+') {
sign = 1;
} else if (curr === '-') {
sign = -1;
} else if (curr >= '0' && curr <= '9') {
let num = curr;
while (i + 1 < str.length && str.charAt(i + 1) >= '0' && str.charAt(i + 1) <= '9') {
num += str.charAt(i + 1);
i++;
}
result += sign * parseInt(num);
}
}
return result;
}
Parentheses
Parentheses serve a dual role: they dictate the order of operations, and in more complex scenarios, they could also signify multiplication. But we're ignoring multiplication for now. So, what's the big deal?

Why Are Parentheses Tricky?
Parentheses create a new "scope" for our calculations. Think of them like those Russian nesting dolls; what happens inside the innermost doll shouldn't affect the outer dolls directly. Parentheses introduce a hierarchical structure to the expression, which complicates our otherwise linear flow of calculations.
The Mighty Stack Data Structure
The classic way to handle this hierarchical structure is by using a stack. Imagine a stack of books; you can only add or remove a book from the top. Similarly, a stack in computer science is a data structure where you can add elements to the top and remove them from the top.
We'll use two stacks for our calculator:
- Result Stack: To keep track of intermediate results.
- Operation Stack: To store the sign (either
+
or-
) before and within the parentheses.
Logic for Handling Parentheses
The stack will help us remember the result
and sign
right before we enter a new set of parentheses. When we close the parentheses, we can pop the top elements from our stacks to restore the previous state and correctly compute the result.
1if (curr === '(') {
2 stack.push(result);
3 result = 0;
4 operationStack.push(sign);
5 sign = 1;
6} else if (curr === ')') {
7 result = operationStack.pop() * result + stack.pop();
8 sign = 1;
9}
By using stacks to manage the state inside parentheses, we can effectively simplify our calculations while respecting the order of operations. So, are you ready to integrate this into our calculator code?
One final caveat-- we also need to handle spaces, but that's easy to accomplish by skipping that iteration.
1if (curr === ' ') {
2 continue;
3}
Complexity of Final Solution
Let n
be the length of the input. We iterate through the input once for O(n)
time complexity, and we can have up to n
elements on the stack (for example having an input only containing (
) for O(n)
space complexity as well.
Let's see it all together now.
The Multiplication and Division Conundrum
So, you've mastered addition, subtraction, and those tricky parentheses. Feeling confident?
Now, let's up the ante with multiplication and division. This is often the follow-up question in interviews, and it adds an extra layer of complexity. Why? Because multiplication and division have higher precedence than addition and subtraction, and they introduce non-linear relationships between the numbers.
The Fundamental Question: Why Stacks?
The stack is a lifeline in this problem for one primary reason: it provides a way to handle different priorities of operations without needing to backtrack. Think of it as a holding area or a waiting room where numbers and operations can wait their turn to be processed in the correct sequence.
Why Does a Stack Work?
A stack works on a last-in, first-out (LIFO) principle. It allows us to temporarily "park" elements that we'll need soon but not immediately. In the case of multiplication and division, this "parking" facility becomes invaluable. It enables us to first collect the operands and then perform the operation when the time is right.
Mental Model with Example: "4 * 3 + 2"
Let's take an example expression: 4 * 3 + 2
. There's no parentheses, so it's easier to follow. We'll use Python for this example.
Here's a step-by-step breakdown:
- Initialization: We initialize
num
to 0,stack
to an empty list, andsign
to+
.
1let [num, stack, sign] = [0, [], "+"];
First Iteration (
4
):- We encounter
4
, sonum
becomes 4.
- We encounter
Second Iteration (
*
):- We hit the
*
. At this point, the previoussign
was+
, so we push4
ontostack
. - We update
sign
to*
.
- We hit the
Third Iteration (
3
):- We encounter
3
, sonum
becomes 3.
- We encounter
Fourth Iteration (
+
):- We hit the
+
. The lastsign
was*
, so we pop4
fromstack
and push4 * 3 = 12
. - We update
sign
to+
.
- We hit the
Fifth Iteration (
2
):- We encounter
2
, sonum
becomes 2.
- We encounter
End of String:
- The last
sign
is+
, so we push2
ontostack
.
- The last
Final Calculation:
- We sum the
stack
[12, 2], which results in14
.
- We sum the
xxxxxxxxxx
console.log(calculator("4*3+2"))
function calculator(s) {
let [num, stack, sign] = [0, [], "+"]
​
for (let i = 0; i < s.length; i++) {
// Check if it's a number
// If there's a previous `num` (num is not 0),
// it's multi-digit (ex. 13), so we extend it
// while processing `3`
if (!isNaN(s[i])) {
num = num * 10 + parseInt(s[i])
}
if (["+", "-", "*", "/"].includes(s[i]) || i === s.length - 1) {
console.log(i, s[i])
if (sign == "+") {
stack.push(num);
} else if (sign == "-") {
stack.push(-num);
} else if (sign == "*") {
// Our multiplication step: the stack.pop()
// will provide the last number we encountered.
stack.push(stack.pop() * num)
} else {
// Division
stack.push(parseInt(stack.pop() / num))
}
num = 0;
sign = s[i];
}
}
Multiplication and Division with Parentheses
We've been focusing on adding and subtracting, but now let's tackle multiplication and division when they're tossed into the mix with parentheses. The challenge is to keep processing our string expression from left to right while still honoring the parentheses and the order of operations. So, let's rethink our approach a bit.
The "Aha" Moment: View Multiplication as Extended Addition
Imagine you're staring at the expression "4*(3+2)"
. We naturally get drawn to the parentheses and think about tackling that first. But what if we reframe it? Instead of seeing it as "4 multiplies (3+2)"
, think of it as "0 add (4 * 5)"
.
Why the Reframe Works
This reframe works because it turns a nested operation into a linear one. The operation that seemed nested within the parentheses is now another operation in the queue. And because we've flattened it out, we can handle it with a left-to-right pass through the string, just like we did with simpler expressions.
Leveraging the Stack for this Reframe
The stack data structure comes to our rescue here. As we go through the string, we keep track of numbers and operations, as usual. But now, when we encounter a parenthesis, we use the stack to "remember" the operation that was happening right before we dove into the parenthesis. This enables us to "flatten" the nested operation into a linear one.
1if ch == "(":
2 # We can still leverage the stack that we're using
3 # by recreating the parentheses expression within the stack
4 stack.append(sign)
5 stack.append('(')
6 # Reset to plus to add the parentheses expression results
7 sign = '+'
Unpacking the Parentheses
When we exit the parenthesis, we unpack the operations and numbers we've stored and perform the calculation. This way, the result of "3+2" in "4*(3+2)" becomes just another number in our queue of operations.
1if ch == ')':
2 # Complete our parentheses expression
3 num, item = 0, stack.pop()
4 while item != '(':
5 num += item
6 item = stack.pop()
7 sign = stack.pop()
Python: Implementing the Strategy

In Python, let's walk through the code step-by-step:
Initialize Variables: We start by initializing our variables. The variable
num
will hold the number we're currently parsing,stack
will hold our numbers and signs, andsign
will hold the current operation (addition by default).PYTHON1num, stack, sign = 0, [], "+"
Loop Through the String: We'll loop through each character in the string. For the last iteration, we append a '+' to ensure we process all numbers.
PYTHON1for i, ch in enumerate(s + '+'):
Detect Numbers: If the character is numeric, we build the current number.
PYTHON1if ch.isnumeric(): 2 num = num * 10 + int(ch)
Handling Parentheses: When we encounter an open parenthesis, we push the current
sign
and parenthesis to the stack, and then resetsign
to '+'.PYTHON1if ch == "(": 2 stack.append(sign) 3 stack.append('(') 4 sign = '+'
Handle Operations and Close Parenthesis: When we encounter any operator or close parenthesis, we perform the respective operation.
PYTHON1if ch in "+-*/)" or i == len(s) - 1: 2 if sign == "+": 3 stack.append(num) 4 elif sign == "-": 5 stack.append(-num) 6 elif sign == "*": 7 stack.append(stack.pop() * num) 8 elif sign == '/': 9 stack.append(int(stack.pop() / num))
Close Parenthesis Logic: If we encounter a close parenthesis, we process the numbers within the parenthesis and sum them up.
PYTHON1if ch == ')': 2 num, item = 0, stack.pop() 3 while item != '(': 4 num += item 5 item = stack.pop() 6 sign = stack.pop()
Reset for Next Loop: Finally, we reset
num
andsign
for the next iteration.PYTHON1else: 2 num = 0 3 sign = ch
Sum it Up: At the end, our
stack
will contain the numbers we need to sum to get the answer.PYTHON1return sum(stack)
Test the Function: We'll use an example to test our function.
PYTHON1print(calculator("4*(3+2)")) # Output should be 20
xxxxxxxxxx
console.log(calculator("4*(3+2)"));
function calculator(s) {
let [num, stack, sign] = [0, [], "+"];
s += '+';
for (let i = 0; i < s.length; i++) {
const ch = s[i];
if (!isNaN(ch)) {
num = num * 10 + parseInt(ch);
}
if (ch === "(") {
stack.push(sign);
stack.push('(');
sign = '+';
}
if ("+-*/)".includes(ch) || i === s.length - 1) {
if (sign === "+") {
stack.push(num);
} else if (sign === "-") {
stack.push(-num);
} else if (sign === "*") {
stack.push(stack.pop() * num);
} else {
stack.push(parseInt(stack.pop() / num));
}
if (ch === ')') {
num = 0;
let item = stack.pop();
while (item !== '(') {
num += item;
item = stack.pop();
One Pager Cheat Sheet
- Let's
program
a very basiccalculator
, handlingpositive
,negative
symbols,parentheses
, andspaces
, without usingeval function
! - In most languages, you can evaluate code strings
quickly and easily
usingeval
. - We can
break down
the problem into parsing and evaluatingsymbols
such asintegers
,signs/operations
,parenthesis
andspaces
, anditerate through
an expression string totransform it
into a form understandable by the machine, withsome caveats
in the processing order. - Adding a negative number to our running result is the key to solving the problem of subtraction.
- We can
keep track
of our sign while converting the signs of numbers by using1
or-1
to represent the+
or-
operations respectively. - Leveraging two
stacks
to keep track of theresult
andoperations
within the parentheses is the key to solving this challenging problem. - We
skip
thatiteration
if thecurrent
character is aspace
. - We can iterate through the input of length
n
inO(n)
time and space complexity. - We can extend our previous solution to handle Multiplication and Division by using a
stack
to store multipliers and dividers and multiplying or dividing the result of eachfor loop
iteration by this stack as it pops values off. By
recreatingthe parentheses expression on a stack, we can still use the left-to-right approach to parse
"4*(3+2)", instead of being "drawn to the parentheses".
This is our final solution.
To visualize the solution and step through the below code, click Visualize the Solution on the right-side menu or the VISUALIZE button in Interactive Mode.
xxxxxxxxxx
}
var assert = require('assert');
​
function calculator(str) {
let result = 0,
sign = 1;
const stack = [],
operationStack = [];
​
for (let i = 0; i < str.length; i++) {
const curr = str.charAt(i);
if (curr === ' ') {
continue;
} else if (curr === '+') {
sign = 1;
} else if (curr === '-') {
sign = -1;
} else if (curr >= '0' && curr <= '9') {
let num = curr;
while (
i + 1 < str.length &&
str.charAt(i + 1) >= '0' &&
str.charAt(i + 1) <= '9'
) {
num += str.charAt(i + 1);
i++;
}
result += sign * parseInt(num);
} else if (curr === '(') {
stack.push(result);
result = 0;
operationStack.push(sign);
sign = 1;
That's all we've got! Let's move on to the next tutorial.
If you had any problems with this tutorial, check out the main forum thread here.