Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
80: Big-O Notation with Rust Examples
A few years ago, I was reviewing a PR from a developer who had written a function to filter out duplicate usernames from a massive CSV import. On his local machine, with a test file of about 500 names, the code ran in a blink. He felt great. But the moment we pushed it to the staging environment and fed it a real production dataset of 100,000 users, the server basically froze. The CPU spiked to 100%, and the request timed out. The culprit? He had used a nested loop—for every name in the list, he scanned the entire "already seen" list to see if it existed. It was a classic case of $O(n^2)$ complexity hitting a wall.
This is why we talk about Big-O notation. It isn't just academic fluff for computer science interviews; it's the language we use to describe how an algorithm's resource requirements grow as the input size grows. When I talk about "time complexity," I'm not talking about seconds or milliseconds—because those change depending on whether you're running on a MacBook M3 or a ten-year-old ThinkPad. Instead, we're talking about the rate of growth.
The Growth Curve of Common Rust Operations
In Rust, you'll encounter a few common complexity classes daily. The most efficient is $O(1)$, or constant time. No matter if your Vec has ten elements or ten million, accessing an element by index—like my_vec[42]—takes the same amount of time. The CPU just calculates a memory offset and jumps there.
Then there's $O(n)$, linear time. If you're searching for a specific value in a Vec using .iter().find(), you might get lucky and find it first, but in the worst case, you have to look at every single element. If the list doubles in size, the time it takes to search doubles. It's a fair trade-off, but it starts to hurt when $n$ gets truly huge.
The real trouble starts with $O(n^2)$, quadratic time. This usually happens when you nest a loop inside another loop over the same collection. If you have 1,000 items, you're doing 1,000,000 operations. If you move to 100,000 items, you're suddenly looking at 10 billion operations. That's how my colleague's import script crashed.
// O(n^2) - The "Danger Zone"
fn has_duplicates(list: &[&str]) -> bool {
for i in 0..list.len() {
for j in i + 1..list.len() {
if list[i] == list[j] {
return true;
}
}
}
false
}
Leveraging Collections for Better Complexity
The secret to fixing $O(n^2)$ problems is usually trading a bit of memory (space complexity) for speed (time complexity). In Rust, this almost always means swapping a Vec for a HashSet or a HashMap. While adding an item to a Vec is $O(1)$, checking if a Vec contains an item is $O(n)$. However, checking if a HashSet contains an item is $O(1)$ on average.
By using a HashSet, we can turn that nested loop into a single pass. We iterate through the list once, checking the set and inserting the value. This transforms the operation from $O(n^2)$ to $O(n)$. The difference is staggering: for 100,000 items, we go from 10 billion checks to just 100,000.
use std::collections::HashSet;
// O(n) - Much more scalable
fn has_duplicates_fast(list: &[&str]) -> bool {
let mut seen = HashSet::new();
for item in list {
if !seen.insert(item) { // insert returns false if the item was already present
return true;
}
}
false
}
You'll also see $O(n \log n)$ frequently, particularly when sorting. Rust's slice::sort() uses a sophisticated algorithm (Timsort) that falls into this category. It's slower than linear time but significantly faster than quadratic time. When you're deciding between sorting a list first and then scanning it, or just using a HashMap, you're essentially doing a Big-O trade-off analysis.
📋 Practical Task
Optimizing the Duplicate Username Detector
You have been handed a piece of legacy code used to verify if a list of usernames contains any duplicates before they are committed to a database. The current implementation uses a nested loop and is causing timeouts in production as the user base grows.
Your Task: Rewrite the check_for_duplicates function. Replace the $O(n^2)$ nested loop approach with a more efficient $O(n)$ implementation using a HashSet. Ensure the function signature remains the same so that the existing tests still pass.
use std::collections::HashSet;
fn check_for_duplicates(usernames: Vec<String>) -> bool {
// TODO: Remove the nested loops and implement an O(n) solution
for i in 0..usernames.len() {
for j in i + 1..usernames.len() {
if usernames[i] == usernames[j] {
return true;
}
}
}
false
}
fn main() {
let users = vec![
"alice".to_string(),
"bob".to_string(),
"charlie".to_string(),
"alice".to_string()
];
if check_for_duplicates(users) {
println!("Duplicates found!");
} else {
println!("All usernames are unique.");
}
}There are no comments for now.