When writing quick test logic or proof-of-concept code, it’s common for developers to prioritize simplicity over performance. One classic example? Nested loops used for data cross-referencing.

Let’s explore a real-world Java example, analyze the problem, and then optimize it the right way.


🚨 The Naive Approach: Simple, but Costly

Suppose you're iterating through a list of users and matching each with corresponding memos by userId. Here’s how it often looks:

for (User user : userTestList) {
    Long userId = user.getUserId();
    for (UserMemo userMemo : userMemoTestList) {
        if (userId.equals(userMemo.getUserId())) {
            String content = userMemo.getContent();
            System.out.println("Processing mock content..." + content);
        }
    }
}

❌ Performance Problem:

  • Time complexity is O(n × m).
  • If each list has 1,000 items, you’re performing 1 million comparisons.
  • Doesn’t scale well as list sizes increase.

✅ The Optimized Approach: Use a HashMap

Instead of scanning the memo list for every user, index the memos by userId to enable constant-time lookups.

🔧 Optimized Java Code

// Step 1: Build a map from userId to memo content list
Map<Long, List<String>> memoMap = new HashMap<>();
for (UserMemo userMemo : userMemoTestList) {
    memoMap
        .computeIfAbsent(userMemo.getUserId(), k -> new ArrayList<>())
        .add(userMemo.getContent());
}

// Step 2: Iterate through users and lookup memo content
for (User user : userTestList) {
    List<String> contents = memoMap.get(user.getUserId());
    if (contents != null) {
        for (String content : contents) {
            System.out.println("Processing mock content..." + content);
        }
    }
}

✅ Advantages:

  • Time complexity drops to O(n + m)
  • Efficient for large datasets
  • Keeps your code readable and maintainable

🧪 Optional: Benchmarking It

To see the actual performance gain, wrap each version in timing logic:

long start = System.nanoTime();
// ... run the loop logic
long end = System.nanoTime();
System.out.println("Elapsed time (ms): " + (end - start) / 1_000_000);

Or, use a profiler like JVisualVM or Java Mission Control for deeper insight.


🧵 Key Takeaway

If your code needs to cross-reference two lists based on a shared key:

  • Avoid nested loops for every lookup.
  • Use a Map to turn lookups into constant-time operations.
  • Small changes make a big difference as your data scales.

💬 Final Tips

  • Use Map instead of Map> if memos are one-to-one.
  • Always check for null when using .get() from the map.
  • Don't forget to measure before and after — it's satisfying to see that performance win.

📌 TL;DR Table

Approach Time Complexity Readability Scalability
Nested Loop O(n × m) ✅ Simple ❌ Slow
Map Lookup O(n + m) ✅ Simple ✅ Fast

That’s it! Clean code doesn’t have to be slow—and performant code doesn’t have to be ugly.