Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
234: Big-O Notation with Java Examples
A few years ago, I was reviewing a pull request from a junior developer who had built a feature to detect duplicate email addresses in a user sign-up list. In the staging environment, with about 200 test users, the code ran in a blink. It looked perfect. But the moment we pushed it to production—where we had 150,000 users—the server basically choked. The request timed out, the CPU spiked to 100%, and the logs were screaming. The culprit was a nested for loop that compared every email to every other email. In his head, it was just "two loops," but in reality, he'd written an algorithm that performed roughly 22 billion operations. That's when I sat him down to talk about Big-O notation.
The Language of Growth Rates
When we talk about Big-O, we aren't talking about how many milliseconds a piece of code takes to run. Your MacBook Pro will run a loop faster than a ten-year-old server, but the efficiency of the algorithm remains the same. Big-O is about how the runtime grows as the input size (which we call n) increases. It’s a way of describing the worst-case scenario so you don't get a nasty surprise when your user base grows.
Think of it as a trajectory. If you have 10 items, an inefficient algorithm might be fine. If you have a million, that same algorithm could take years to complete. By labeling code as $O(1)$, $O(n)$, or $O(n^2)$, we can predict exactly when a solution is going to break before we ever deploy it.
Measuring Time with Java Code
Let's look at this through some actual Java examples. The gold standard is $O(1)$, or Constant Time. This means no matter how huge your data set is, the operation always takes the same amount of time. Accessing an element in an array by its index is the classic example:
int[] scores = {92, 85, 77, 98, 60};
int topScore = scores[0]; // This is O(1)
Whether the array has 5 elements or 5 million, jumping to index 0 takes exactly one step. Now, most of the logic you'll write will be $O(n)$, or Linear Time. This happens when the time spent grows in direct proportion to the input. A simple search through an ArrayList is $O(n)$:
public boolean containsValue(List<String> list, String target) {
for (String item : list) {
if (item.equals(target)) return true;
}
return false;
}
If the list has 10 items, you might check 10 times. If it has a million, you might check a million times. It's a straight line on a graph. Then we hit the danger zone: $O(n^2)$, or Quadratic Time. This usually happens when you nest loops. This was the "production killer" I mentioned earlier:
public void findDuplicates(List<String> emails) {
for (int i = 0; i < emails.size(); i++) {
for (int j = i + 1; j < emails.size(); j++) {
if (emails.get(i).equals(emails.get(j))) {
System.out.println("Duplicate found: " + emails.get(i));
}
}
}
}
For every single email, you are iterating through the entire list again. If you have 1,000 emails, you're doing 1,000,000 comparisons. This is where performance falls off a cliff.
Choosing the Right Tool for the Job
The secret to being a senior engineer isn't just knowing how to write code that works, but knowing which data structure turns an $O(n^2)$ problem into an $O(n)$ problem. In the duplicate email example above, we can swap the nested loop for a HashSet. Since checking if an item exists in a HashSet is $O(1)$, the entire process becomes $O(n)$.
I'll leave you with a quick mental cheat sheet: if you see a single loop, think $O(n)$. If you see nested loops over the same collection, alarm bells should go off for $O(n^2)$. If you see a search space being cut in half every time (like a binary search on a sorted array), you're looking at $O(\log n)$, which is incredibly efficient. Always ask yourself: "What happens to this method if the input size grows by 1,000x?" If the answer is "the server crashes," it's time to rethink your Big-O.
📋 Practical Task
Exercise: Optimizing a Duplicate User Detector
You have been handed a legacy method used to find if a list of usernames contains any duplicates. The current implementation uses nested loops and is causing performance issues in production. Your task is to rewrite the hasDuplicates method to improve its time complexity from $O(n^2)$ to $O(n)$.
import java.util.*;
public class UserRegistry {
// CURRENT IMPLEMENTATION: O(n^2)
public boolean hasDuplicates(List<String> usernames) {
for (int i = 0; i < usernames.size(); i++) {
for (int j = i + 1; j < usernames.size(); j++) {
if (usernames.get(i).equals(usernames.get(j))) {
return true;
}
}
}
return false;
}
public static void main(String[] args) {
UserRegistry registry = new UserRegistry();
List<String> users = Arrays.asList("alice", "bob", "charlie", "alice");
System.out.println("Duplicates found: " + registry.hasDuplicates(users));
}
}
Requirements:
- Rewrite the
hasDuplicatesmethod using aHashSetto track usernames you have already seen. - The method must return
trueas soon as the first duplicate is encountered. - Ensure the time complexity of your new solution is $O(n)$.
There are no comments for now.