Java
Programming Optimization
Performance Improvement
Computer Science
Arithmetic Operations

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.

Practice algorithms

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:

java
2 * i * i

means:

java
(2 * i) * i

while:

java
2 * (i * i)

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:

java
int a = (2 * i) * i;
int b = 2 * (i * i);

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:

java
1import org.openjdk.jmh.annotations.Benchmark;
2
3public class ArithmeticBench {
4    private int i = 12345;
5
6    @Benchmark
7    public int grouped() {
8        return 2 * (i * i);
9    }
10
11    @Benchmark
12    public int leftAssociative() {
13        return 2 * i * i;
14    }
15}

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:

java
2 * (i * i)

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 * i is evaluated as (2 * i) * i, while 2 * (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
Course
Intermediate
27 lessons
15 hours
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 course
Track 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.

Practice algorithms

All Rights Reserved.