Conditionals — Optimize the Code¶
Practice optimizing slow, inefficient, or resource-heavy Java code related to Conditionals. Each exercise contains working but suboptimal code — your job is to make it faster, leaner, or more efficient.
How to Use¶
- Read the slow code and understand what it does
- Identify the performance bottleneck
- Write your optimized version
- Compare with the solution and benchmark results
- Understand why the optimization works
Difficulty Levels¶
| Level | Focus |
|---|---|
| Easy | Obvious inefficiencies, simple fixes |
| Medium | Algorithmic improvements, structure changes |
| Hard | Branch prediction, lookup tables, JIT-aware code |
Optimization Categories¶
| Category | Icon | Description |
|---|---|---|
| Memory | mem | Reduce allocations, reuse objects, avoid copies |
| CPU | cpu | Better algorithms, fewer operations, cache efficiency |
| Concurrency | conc | Better parallelism, reduce contention |
| I/O | io | Batch operations, buffering |
Exercise 1: If-Else Chain to Switch (Easy, cpu)¶
What the code does: Maps an HTTP status code to a status message.
The problem: A long if-else chain is less readable and potentially slower than a switch statement for direct value matching.
public class Main {
public static String getStatusMessage(int code) {
if (code == 200) {
return "OK";
} else if (code == 201) {
return "Created";
} else if (code == 301) {
return "Moved Permanently";
} else if (code == 400) {
return "Bad Request";
} else if (code == 401) {
return "Unauthorized";
} else if (code == 403) {
return "Forbidden";
} else if (code == 404) {
return "Not Found";
} else if (code == 500) {
return "Internal Server Error";
} else if (code == 502) {
return "Bad Gateway";
} else if (code == 503) {
return "Service Unavailable";
} else {
return "Unknown";
}
}
public static void main(String[] args) {
int[] codes = {200, 404, 500, 201, 403, 502, 301, 401, 503, 400};
long start = System.nanoTime();
for (int i = 0; i < 1_000_000; i++) {
for (int code : codes) {
getStatusMessage(code);
}
}
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.println("If-else chain: " + elapsed + " ms");
}
}
Current benchmark:
Hint
The JVM can compile a `switch` on `int` to a tableswitch or lookupswitch bytecode instruction, which is more efficient than sequential comparisons.Optimized Code
public class Main {
public static String getStatusMessage(int code) {
switch (code) {
case 200: return "OK";
case 201: return "Created";
case 301: return "Moved Permanently";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 403: return "Forbidden";
case 404: return "Not Found";
case 500: return "Internal Server Error";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
default: return "Unknown";
}
}
public static void main(String[] args) {
int[] codes = {200, 404, 500, 201, 403, 502, 301, 401, 503, 400};
long start = System.nanoTime();
for (int i = 0; i < 1_000_000; i++) {
for (int code : codes) {
getStatusMessage(code);
}
}
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.println("Switch: " + elapsed + " ms");
}
}
Learn More
**Why this works:** The `switch` statement compiles to `lookupswitch` or `tableswitch` JVM instructions. `tableswitch` uses a direct jump table (O(1)), while `lookupswitch` uses binary search (O(log n)). An if-else chain always does sequential O(n) comparisons. **When to apply:** When comparing a single variable against 3+ constant values. **When NOT to apply:** For range checks (e.g., `score >= 90`) or complex boolean expressions — if-else is the only option.Exercise 2: Redundant Boolean Comparison (Easy, cpu)¶
What the code does: Checks various boolean conditions and prints results.
The problem: Explicitly comparing booleans to true/false is redundant and adds unnecessary operations.
public class Main {
public static void main(String[] args) {
boolean isAdmin = true;
boolean isActive = true;
boolean isBanned = false;
if (isAdmin == true && isActive == true && isBanned == false) {
System.out.println("Access granted");
} else {
System.out.println("Access denied");
}
String result = (isAdmin == true) ? "Admin" : "User";
System.out.println("Role: " + result);
if (isBanned == false) {
System.out.println("User is not banned");
}
}
}
Current benchmark:
Hint
A `boolean` is already `true` or `false`. Why compare it to `true` again?Optimized Code
public class Main {
public static void main(String[] args) {
boolean isAdmin = true;
boolean isActive = true;
boolean isBanned = false;
if (isAdmin && isActive && !isBanned) { // direct boolean usage
System.out.println("Access granted");
} else {
System.out.println("Access denied");
}
String result = isAdmin ? "Admin" : "User"; // no == true needed
System.out.println("Role: " + result);
if (!isBanned) { // use ! instead of == false
System.out.println("User is not banned");
}
}
}
Learn More
**Why this works:** While the JIT compiler may optimize these to the same bytecode, direct boolean usage is the Java convention. It reduces bytecode size and avoids unnecessary `if_icmpeq` instructions. **When to apply:** Always. There is no reason to write `== true` or `== false` in Java.Exercise 3: Repeated Condition Evaluation (Easy, cpu)¶
What the code does: Categorizes a temperature value into a weather description.
The problem: Conditions are evaluated redundantly — ranges that could be implied by else-if are explicitly re-checked.
public class Main {
public static String categorize(double temp) {
if (temp < 0) {
return "Freezing";
}
if (temp >= 0 && temp < 10) {
return "Cold";
}
if (temp >= 10 && temp < 20) {
return "Cool";
}
if (temp >= 20 && temp < 30) {
return "Warm";
}
if (temp >= 30 && temp < 40) {
return "Hot";
}
if (temp >= 40) {
return "Extreme";
}
return "Unknown";
}
public static void main(String[] args) {
double[] temps = {-5, 3, 15, 25, 35, 45, 0, 10, 20, 30};
long start = System.nanoTime();
for (int i = 0; i < 1_000_000; i++) {
for (double t : temps) {
categorize(t);
}
}
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.println("Categorize: " + elapsed + " ms");
}
}
Current benchmark:
Hint
If `temp < 0` is false, you already know `temp >= 0`. Why check it again in the next condition?Optimized Code
public class Main {
public static String categorize(double temp) {
if (temp < 0) {
return "Freezing";
} else if (temp < 10) { // no need for temp >= 0, implied by else
return "Cold";
} else if (temp < 20) { // no need for temp >= 10
return "Cool";
} else if (temp < 30) { // no need for temp >= 20
return "Warm";
} else if (temp < 40) { // no need for temp >= 30
return "Hot";
} else {
return "Extreme";
}
}
public static void main(String[] args) {
double[] temps = {-5, 3, 15, 25, 35, 45, 0, 10, 20, 30};
long start = System.nanoTime();
for (int i = 0; i < 1_000_000; i++) {
for (double t : temps) {
categorize(t);
}
}
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.println("Categorize: " + elapsed + " ms");
}
}
Learn More
**Why this works:** Each redundant comparison costs a CPU cycle. In an else-if chain, the control flow guarantees that previous conditions were false, making re-checking wasteful. With 6 ranges, the worst case drops from 11 comparisons to 6. **When to apply:** Any time you have sequential, mutually exclusive range checks.Exercise 4: Switch Expression (Java 14+) (Medium, cpu)¶
What the code does: Converts a month number to a season name.
The problem: A traditional switch with break statements is verbose and error-prone. Java 14+ switch expressions are more concise and can be faster.
public class Main {
public static String getSeason(int month) {
String season;
switch (month) {
case 12:
season = "Winter";
break;
case 1:
season = "Winter";
break;
case 2:
season = "Winter";
break;
case 3:
season = "Spring";
break;
case 4:
season = "Spring";
break;
case 5:
season = "Spring";
break;
case 6:
season = "Summer";
break;
case 7:
season = "Summer";
break;
case 8:
season = "Summer";
break;
case 9:
season = "Autumn";
break;
case 10:
season = "Autumn";
break;
case 11:
season = "Autumn";
break;
default:
season = "Invalid";
break;
}
return season;
}
public static void main(String[] args) {
long start = System.nanoTime();
for (int i = 0; i < 1_000_000; i++) {
for (int m = 1; m <= 12; m++) {
getSeason(m);
}
}
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.println("Season lookup: " + elapsed + " ms");
}
}
Current benchmark:
Hint
Java 14 switch expressions allow grouping multiple cases with commas and using the arrow `->` syntax. This eliminates `break` entirely and is compiled more efficiently.Optimized Code
public class Main {
public static String getSeason(int month) {
return switch (month) {
case 12, 1, 2 -> "Winter";
case 3, 4, 5 -> "Spring";
case 6, 7, 8 -> "Summer";
case 9, 10, 11 -> "Autumn";
default -> "Invalid";
};
}
public static void main(String[] args) {
long start = System.nanoTime();
for (int i = 0; i < 1_000_000; i++) {
for (int m = 1; m <= 12; m++) {
getSeason(m);
}
}
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.println("Season lookup: " + elapsed + " ms");
}
}
Learn More
**Why this works:** Switch expressions compile to the same efficient `tableswitch` bytecode, but the grouped syntax lets the JVM generate a denser jump table. The arrow syntax also eliminates the risk of fall-through bugs. **When to apply:** Any Java 14+ project where you use switch for value mapping. Always prefer switch expressions over traditional switch when possible.Exercise 5: Lookup Table Instead of Conditionals (Medium, cpu)¶
What the code does: Maps a numeric grade (0-100) to a letter grade.
The problem: An if-else chain performs multiple comparisons for every call. A pre-computed lookup table gives O(1) access.
public class Main {
public static char getGrade(int score) {
if (score >= 90) {
return 'A';
} else if (score >= 80) {
return 'B';
} else if (score >= 70) {
return 'C';
} else if (score >= 60) {
return 'D';
} else {
return 'F';
}
}
public static void main(String[] args) {
long start = System.nanoTime();
int countA = 0, countB = 0, countC = 0, countD = 0, countF = 0;
for (int i = 0; i < 10_000_000; i++) {
int score = i % 101; // 0-100
char g = getGrade(score);
switch (g) {
case 'A': countA++; break;
case 'B': countB++; break;
case 'C': countC++; break;
case 'D': countD++; break;
case 'F': countF++; break;
}
}
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.println("A=" + countA + " B=" + countB + " C=" + countC
+ " D=" + countD + " F=" + countF);
System.out.println("If-else grading: " + elapsed + " ms");
}
}
Current benchmark:
Hint
Can you pre-compute all 101 possible grades into an array and just look up the answer by index?Optimized Code
public class Main {
// Pre-computed lookup table: index = score, value = grade
private static final char[] GRADE_TABLE = new char[101];
static {
for (int i = 0; i <= 100; i++) {
if (i >= 90) GRADE_TABLE[i] = 'A';
else if (i >= 80) GRADE_TABLE[i] = 'B';
else if (i >= 70) GRADE_TABLE[i] = 'C';
else if (i >= 60) GRADE_TABLE[i] = 'D';
else GRADE_TABLE[i] = 'F';
}
}
public static char getGrade(int score) {
return GRADE_TABLE[score]; // O(1) array lookup
}
public static void main(String[] args) {
long start = System.nanoTime();
int countA = 0, countB = 0, countC = 0, countD = 0, countF = 0;
for (int i = 0; i < 10_000_000; i++) {
int score = i % 101;
char g = getGrade(score);
switch (g) {
case 'A': countA++; break;
case 'B': countB++; break;
case 'C': countC++; break;
case 'D': countD++; break;
case 'F': countF++; break;
}
}
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.println("A=" + countA + " B=" + countB + " C=" + countC
+ " D=" + countD + " F=" + countF);
System.out.println("Lookup grading: " + elapsed + " ms");
}
}
Learn More
**Why this works:** Array access is a single memory read with no branches. Branch mispredictions cost ~15 CPU cycles on modern processors. A 101-byte lookup table easily fits in L1 data cache (typically 32-64 KB). **When to apply:** When the input domain is small and bounded (e.g., 0-100, enum ordinals, ASCII characters). **When NOT to apply:** When the input domain is large or unbounded — the table would waste memory.Exercise 6: Pattern Matching for instanceof (Java 16+) (Medium, cpu)¶
What the code does: Processes different shapes and calculates their area.
The problem: Traditional instanceof checks followed by explicit casts are verbose, error-prone, and cause double type-checking at the JVM level.
public class Main {
static abstract class Shape {}
static class Circle extends Shape {
double radius;
Circle(double r) { this.radius = r; }
}
static class Rectangle extends Shape {
double width, height;
Rectangle(double w, double h) { this.width = w; this.height = h; }
}
static class Triangle extends Shape {
double base, height;
Triangle(double b, double h) { this.base = b; this.height = h; }
}
public static double getArea(Shape shape) {
if (shape instanceof Circle) {
Circle c = (Circle) shape; // redundant cast
return Math.PI * c.radius * c.radius;
} else if (shape instanceof Rectangle) {
Rectangle r = (Rectangle) shape; // redundant cast
return r.width * r.height;
} else if (shape instanceof Triangle) {
Triangle t = (Triangle) shape; // redundant cast
return 0.5 * t.base * t.height;
}
throw new IllegalArgumentException("Unknown shape");
}
public static void main(String[] args) {
Shape[] shapes = {
new Circle(5), new Rectangle(4, 6), new Triangle(3, 8),
new Circle(2), new Rectangle(10, 3), new Triangle(7, 4)
};
long start = System.nanoTime();
double total = 0;
for (int i = 0; i < 1_000_000; i++) {
for (Shape s : shapes) {
total += getArea(s);
}
}
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.println("Total area: " + total);
System.out.println("Time: " + elapsed + " ms");
}
}
Current benchmark:
Hint
Java 16+ pattern matching for `instanceof` combines the type check and variable declaration into a single step. This eliminates redundant casts and can be optimized better by the JIT.Optimized Code
public class Main {
static abstract class Shape {}
static class Circle extends Shape {
double radius;
Circle(double r) { this.radius = r; }
}
static class Rectangle extends Shape {
double width, height;
Rectangle(double w, double h) { this.width = w; this.height = h; }
}
static class Triangle extends Shape {
double base, height;
Triangle(double b, double h) { this.base = b; this.height = h; }
}
public static double getArea(Shape shape) {
if (shape instanceof Circle c) { // pattern matching
return Math.PI * c.radius * c.radius;
} else if (shape instanceof Rectangle r) { // no explicit cast
return r.width * r.height;
} else if (shape instanceof Triangle t) { // type + binding in one
return 0.5 * t.base * t.height;
}
throw new IllegalArgumentException("Unknown shape");
}
public static void main(String[] args) {
Shape[] shapes = {
new Circle(5), new Rectangle(4, 6), new Triangle(3, 8),
new Circle(2), new Rectangle(10, 3), new Triangle(7, 4)
};
long start = System.nanoTime();
double total = 0;
for (int i = 0; i < 1_000_000; i++) {
for (Shape s : shapes) {
total += getArea(s);
}
}
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.println("Total area: " + total);
System.out.println("Time: " + elapsed + " ms");
}
}
Learn More
**Why this works:** Traditional `instanceof` + cast performs the type check twice at the bytecode level (once for `instanceof`, once for `checkcast`). Pattern matching fuses them into a single operation. The JIT compiler can also reason better about the flow. **When to apply:** Always on Java 16+ when you need instanceof followed by a cast. There is no downside.Exercise 7: Avoiding Repeated Method Calls in Conditions (Medium, cpu)¶
What the code does: Validates and categorizes a string input based on multiple conditions.
The problem: The same expensive method is called multiple times across different branches.
public class Main {
public static String categorize(String input) {
if (input != null && input.trim().length() > 0 && input.trim().length() <= 5) {
return "Short: " + input.trim();
} else if (input != null && input.trim().length() > 5 && input.trim().length() <= 20) {
return "Medium: " + input.trim();
} else if (input != null && input.trim().length() > 20) {
return "Long: " + input.trim();
} else {
return "Empty or null";
}
}
public static void main(String[] args) {
String[] inputs = {" hi ", " hello world ", " this is a much longer string ", null, ""};
long start = System.nanoTime();
for (int i = 0; i < 1_000_000; i++) {
for (String s : inputs) {
categorize(s);
}
}
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.println("Time: " + elapsed + " ms");
}
}
Current benchmark:
Hint
Count how many times `input.trim()` is called in the worst case. Can you call it once and reuse the result?Optimized Code
public class Main {
public static String categorize(String input) {
if (input == null) {
return "Empty or null";
}
String trimmed = input.trim(); // call trim() ONCE
int len = trimmed.length(); // call length() ONCE
if (len == 0) {
return "Empty or null";
} else if (len <= 5) {
return "Short: " + trimmed;
} else if (len <= 20) {
return "Medium: " + trimmed;
} else {
return "Long: " + trimmed;
}
}
public static void main(String[] args) {
String[] inputs = {" hi ", " hello world ", " this is a much longer string ", null, ""};
long start = System.nanoTime();
for (int i = 0; i < 1_000_000; i++) {
for (String s : inputs) {
categorize(s);
}
}
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.println("Time: " + elapsed + " ms");
}
}
Learn More
**Why this works:** `String.trim()` allocates a new String object (unless no trimming is needed). Each call in the original code creates garbage for the GC. Storing the result in a local variable avoids redundant allocations and computation. **When to apply:** Whenever the same method appears multiple times in a conditional chain and the method is not trivially cheap (i.e., it allocates memory or performs computation).Exercise 8: Branch Prediction — Order Conditions by Frequency (Hard, cpu)¶
What the code does: Classifies log levels in a high-throughput logging system.
The problem: The if-else chain checks rare cases first. In real systems, DEBUG and INFO logs dominate — but they are checked last.
public class Main {
public static int classify(int level) {
// 0=DEBUG, 1=INFO, 2=WARN, 3=ERROR, 4=FATAL
// In production: 70% DEBUG, 20% INFO, 5% WARN, 3% ERROR, 2% FATAL
if (level == 4) { // FATAL — rarest (2%)
return 5;
} else if (level == 3) { // ERROR — rare (3%)
return 4;
} else if (level == 2) { // WARN — uncommon (5%)
return 3;
} else if (level == 1) { // INFO — common (20%)
return 2;
} else { // DEBUG — most common (70%)
return 1;
}
}
public static void main(String[] args) {
// Simulate realistic distribution
int[] levels = new int[10000];
java.util.Random rng = new java.util.Random(42);
for (int i = 0; i < levels.length; i++) {
int r = rng.nextInt(100);
if (r < 70) levels[i] = 0; // 70% DEBUG
else if (r < 90) levels[i] = 1; // 20% INFO
else if (r < 95) levels[i] = 2; // 5% WARN
else if (r < 98) levels[i] = 3; // 3% ERROR
else levels[i] = 4; // 2% FATAL
}
long start = System.nanoTime();
long sum = 0;
for (int i = 0; i < 100_000; i++) {
for (int lvl : levels) {
sum += classify(lvl);
}
}
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.println("Sum: " + sum + ", Time: " + elapsed + " ms");
}
}
Current benchmark:
Hint
The CPU's branch predictor works best when the first condition is almost always true. If 70% of calls are DEBUG but DEBUG is checked last, every call traverses 4 failed branches first.Optimized Code
public class Main {
public static int classify(int level) {
// Order by frequency: most common first
if (level == 0) { // DEBUG — 70% of traffic
return 1;
} else if (level == 1) { // INFO — 20%
return 2;
} else if (level == 2) { // WARN — 5%
return 3;
} else if (level == 3) { // ERROR — 3%
return 4;
} else { // FATAL — 2%
return 5;
}
}
public static void main(String[] args) {
int[] levels = new int[10000];
java.util.Random rng = new java.util.Random(42);
for (int i = 0; i < levels.length; i++) {
int r = rng.nextInt(100);
if (r < 70) levels[i] = 0;
else if (r < 90) levels[i] = 1;
else if (r < 95) levels[i] = 2;
else if (r < 98) levels[i] = 3;
else levels[i] = 4;
}
long start = System.nanoTime();
long sum = 0;
for (int i = 0; i < 100_000; i++) {
for (int lvl : levels) {
sum += classify(lvl);
}
}
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.println("Sum: " + sum + ", Time: " + elapsed + " ms");
}
}
Learn More
**Why this works:** In an if-else chain, every condition is evaluated sequentially until one matches. By checking the most frequent case first, you minimize the average number of branch instructions. Additionally, the CPU branch predictor becomes highly accurate because the first branch is almost always taken, reducing pipeline stalls. **When to apply:** Any if-else chain in a hot path where the distribution of inputs is known and uneven.Exercise 9: Enum with Behavior Instead of Switch (Hard, cpu)¶
What the code does: Calculates shipping cost based on shipping method.
The problem: Every time a new shipping method is added, the switch must be updated in multiple places — violating the Open-Closed Principle. Enum methods enable polymorphic dispatch which the JVM can inline.
public class Main {
static final int STANDARD = 0;
static final int EXPRESS = 1;
static final int OVERNIGHT = 2;
static final int DRONE = 3;
public static double getShippingCost(int method, double weight) {
switch (method) {
case STANDARD:
return weight * 1.5;
case EXPRESS:
return weight * 3.0 + 5.0;
case OVERNIGHT:
return weight * 5.0 + 15.0;
case DRONE:
return weight * 8.0 + 25.0;
default:
throw new IllegalArgumentException("Unknown method: " + method);
}
}
public static double getDeliveryDays(int method) {
switch (method) {
case STANDARD: return 7;
case EXPRESS: return 3;
case OVERNIGHT: return 1;
case DRONE: return 0.5;
default: throw new IllegalArgumentException("Unknown method: " + method);
}
}
public static void main(String[] args) {
int[] methods = {STANDARD, EXPRESS, OVERNIGHT, DRONE};
double[] weights = {2.5, 1.0, 5.0, 0.5};
long start = System.nanoTime();
double totalCost = 0;
double totalDays = 0;
for (int i = 0; i < 5_000_000; i++) {
for (int j = 0; j < methods.length; j++) {
totalCost += getShippingCost(methods[j], weights[j]);
totalDays += getDeliveryDays(methods[j]);
}
}
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.println("Cost: " + totalCost + ", Days: " + totalDays);
System.out.println("Time: " + elapsed + " ms");
}
}
Current benchmark:
Hint
An enum with abstract methods gives each constant its own implementation. The JVM can use virtual dispatch or even inline the methods, and adding a new constant forces you to implement all methods — no risk of forgetting a case.Optimized Code
public class Main {
enum ShippingMethod {
STANDARD {
public double cost(double weight) { return weight * 1.5; }
public double deliveryDays() { return 7; }
},
EXPRESS {
public double cost(double weight) { return weight * 3.0 + 5.0; }
public double deliveryDays() { return 3; }
},
OVERNIGHT {
public double cost(double weight) { return weight * 5.0 + 15.0; }
public double deliveryDays() { return 1; }
},
DRONE {
public double cost(double weight) { return weight * 8.0 + 25.0; }
public double deliveryDays() { return 0.5; }
};
public abstract double cost(double weight);
public abstract double deliveryDays();
}
public static void main(String[] args) {
ShippingMethod[] methods = ShippingMethod.values();
double[] weights = {2.5, 1.0, 5.0, 0.5};
long start = System.nanoTime();
double totalCost = 0;
double totalDays = 0;
for (int i = 0; i < 5_000_000; i++) {
for (int j = 0; j < methods.length; j++) {
totalCost += methods[j].cost(weights[j]);
totalDays += methods[j].deliveryDays();
}
}
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.println("Cost: " + totalCost + ", Days: " + totalDays);
System.out.println("Time: " + elapsed + " ms");
}
}
Learn More
**Why this works:** The JVM's JIT compiler can devirtualize method calls on enum constants because there are a fixed number of implementations. With a small number of constants (<= 4), the JIT often inlines all paths using type profiling. This eliminates the `lookupswitch` overhead entirely. **When to apply:** When you have switch statements on the same set of constants in multiple places. Enum with behavior is the classic refactoring for this pattern.Exercise 10: Branchless Conditional with Math (Hard, cpu)¶
What the code does: Clamps a value to a range [min, max].
The problem: The if-else version creates branches that can cause CPU pipeline stalls when the input is unpredictable.
public class Main {
public static int clamp(int value, int min, int max) {
if (value < min) {
return min;
} else if (value > max) {
return max;
} else {
return value;
}
}
public static void main(String[] args) {
java.util.Random rng = new java.util.Random(42);
int[] values = new int[100_000];
for (int i = 0; i < values.length; i++) {
values[i] = rng.nextInt(200) - 50; // range: -50 to 149
}
long start = System.nanoTime();
long sum = 0;
for (int iter = 0; iter < 10_000; iter++) {
for (int v : values) {
sum += clamp(v, 0, 100);
}
}
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.println("Sum: " + sum + ", Time: " + elapsed + " ms");
}
}
Current benchmark:
Hint
`Math.min()` and `Math.max()` are JVM intrinsics that can compile to CMOV (conditional move) instructions — branchless on x86. Can you express clamping with just these two calls?Optimized Code
public class Main {
public static int clamp(int value, int min, int max) {
return Math.max(min, Math.min(value, max)); // branchless via CMOV
}
public static void main(String[] args) {
java.util.Random rng = new java.util.Random(42);
int[] values = new int[100_000];
for (int i = 0; i < values.length; i++) {
values[i] = rng.nextInt(200) - 50;
}
long start = System.nanoTime();
long sum = 0;
for (int iter = 0; iter < 10_000; iter++) {
for (int v : values) {
sum += clamp(v, 0, 100);
}
}
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.println("Sum: " + sum + ", Time: " + elapsed + " ms");
}
}
Learn More
**Why this works:** Modern x86 CPUs have a CMOV instruction that performs a conditional move without a branch. The JVM recognizes `Math.min` and `Math.max` as intrinsics and compiles them to CMOV. With random input, branch prediction accuracy is around 50% for the if-else version, costing ~15 cycles per misprediction. CMOV has a fixed cost of ~1 cycle. **When to apply:** Clamping, abs, min/max operations in hot loops with unpredictable input patterns. **When NOT to apply:** If the input is highly predictable (e.g., almost always in range), the branch predictor works well and the if-else version can be equally fast. **Note:** Java 21+ adds `Math.clamp(value, min, max)` as a built-in method.Score Card¶
| Exercise | Difficulty | Category | Found bottleneck? | Your improvement | Target improvement |
|---|---|---|---|---|---|
| 1 | Easy | cpu | ☐ | ___ x | 1.6x |
| 2 | Easy | cpu | ☐ | ___ x | 1.1x |
| 3 | Easy | cpu | ☐ | ___ x | 1.4x |
| 4 | Medium | cpu | ☐ | ___ x | 1.2x |
| 5 | Medium | cpu | ☐ | ___ x | 2.2x |
| 6 | Medium | cpu | ☐ | ___ x | 1.2x |
| 7 | Medium | cpu | ☐ | ___ x | 2.4x |
| 8 | Hard | cpu | ☐ | ___ x | 2.6x |
| 9 | Hard | cpu | ☐ | ___ x | 1.4x |
| 10 | Hard | cpu | ☐ | ___ x | 2.1x |
Rating:¶
- 10/10 bottlenecks found — Senior-level Java optimization skills
- 7-9/10 — Solid middle-level understanding
- 4-6/10 — Good junior, keep practicing
- < 4/10 — Review the topic fundamentals first