Why is 2 * (i * i) faster than 2 * i * i in Java?
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.
Introduction
If a benchmark shows 2 * (i * i) faster than 2 * i * i in Java, the first thing to remember is that both expressions are mathematically similar but not necessarily equivalent under Java's exact integer-overflow rules. The second thing to remember is that tiny arithmetic benchmarks are highly sensitive to JIT optimizations, so you should not assume one spelling is universally faster without careful measurement.
Java evaluates 2 * i * i left to right
In Java, * is left-associative, so:
means:
while:
forces the multiplication of i * i first.
Because Java integer arithmetic has defined overflow behavior, the JVM cannot always freely reorder these expressions as if overflow did not matter. That means the JIT may see two expressions with slightly different optimization constraints.
Overflow semantics are the key nuance
Consider int arithmetic. These two expressions can overflow at different intermediate steps:
Even if both end up overflowing eventually, the exact wrapped intermediate value can differ depending on where overflow occurs first. Since Java specifies that behavior, the compiler must preserve it.
That is why "the compiler should obviously treat them as the same" is not always true for int or long.
Why one may benchmark faster
If 2 * (i * i) benchmarks faster in a particular test, possible reasons include:
- the JIT recognizes a simpler optimization opportunity in that form
- multiplication by a constant is optimized differently after the inner product
- the generated machine code differs slightly because reassociation is restricted
- the benchmark itself amplifies noise or dead-code elimination effects
In other words, the difference is usually about the optimizer and Java's arithmetic semantics, not about parentheses being magically faster by themselves.
Benchmark carefully with JMH
Microbenchmarks in Java are notoriously easy to get wrong. Use JMH instead of timing loops manually:
JMH helps avoid common benchmark mistakes such as:
- dead-code elimination
- constant folding surprises
- warm-up effects
- misleading timer resolution
Without a proper harness, tiny arithmetic timing claims are often meaningless.
Readability versus micro-optimization
If you are not writing performance-critical numeric code, readability matters more than shaving an uncertain few cycles off a trivial expression.
Sometimes:
is clearer because it explicitly communicates "double the square of i." That alone can be a valid reason to prefer it, independent of performance.
If performance truly matters, benchmark the real surrounding code instead of trusting a micro-optimization rumor.
Common Pitfalls
The biggest mistake is assuming a tiny benchmark difference generalizes everywhere. The JVM, CPU, data types, and benchmark harness all affect the result.
Another mistake is ignoring integer overflow semantics. In Java, the compiler cannot always reorder arithmetic freely because overflow behavior is defined.
Developers also benchmark naive loops and accidentally measure JIT warm-up or dead-code elimination instead of the arithmetic itself.
Finally, do not rewrite production code for a microbenchmark unless the surrounding workload proves the change matters.
Summary
- '
2 * i * iis evaluated as(2 * i) * i, while2 * (i * i)forces a different grouping.' - Because Java defines integer overflow behavior, those groupings are not always interchangeable for optimization.
- Any speed difference is usually about JIT code generation, not about parentheses inherently being faster.
- Use JMH if you want a meaningful benchmark.
- Prefer clarity first unless profiling shows the arithmetic form matters in real code.
Related reading
- Why is accessing any single element in an array done in constant time O1 ?
- Why is an even-odd split 'faster' for MergeSort?
- Why is an unreferenced object not finalized immediately after a call to GC.Collect?
- why is async-await much slower than promises when running them together
- Why is a combiner needed for reduce method that converts type in java 8
- Why is a ConcurrentModificationException thrown and how to debug it
- Why is builtin sorted slower for a list containing descending numbers if each number appears twice consecutively?
- Why is Clickhouse slower than PostgreSQL?

DSA Fundamentals
Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.