What are the trade-offs between indexmap::IndexMap::get_index_of and get for index vs key lookups?

IndexMap::get performs a hash-based lookup with O(1) average complexity by key, while get_index_of finds the index position of a key in the insertion order with O(1) average complexity—both are fast lookups, but they return different results: get returns a reference to the value, while get_index_of returns the index position of the key-value pair. The trade-off is between direct value access (get) and positional information (get_index_of), with the latter enabling index-based operations like iteration order and position-aware mutations.

Basic get Usage

use indexmap::IndexMap;
 
fn basic_get() {
    let mut map = IndexMap::new();
    map.insert("apple", 1);
    map.insert("banana", 2);
    map.insert("cherry", 3);
    
    // get: Retrieve value by key (O(1) average)
    let value = map.get("banana");
    println!("Value: {:?}", value);  // Some(&2)
    
    // Returns None if key doesn't exist
    let missing = map.get("durian");
    println!("Missing: {:?}", missing);  // None
    
    // get_mut: Get mutable reference to value
    if let Some(value) = map.get_mut("banana") {
        *value *= 10;
    }
    println!("Modified: {:?}", map.get("banana"));  // Some(&20)
}

get returns Option<&V>—a reference to the value if found.

Basic get_index_of Usage

use indexmap::IndexMap;
 
fn basic_get_index_of() {
    let mut map = IndexMap::new();
    map.insert("apple", 1);
    map.insert("banana", 2);
    map.insert("cherry", 3);
    
    // get_index_of: Find position of key (O(1) average)
    let index = map.get_index_of("banana");
    println!("Index: {:?}", index);  // Some(1)
    
    // Returns None if key doesn't exist
    let missing = map.get_index_of("durian");
    println!("Missing: {:?}", missing);  // None
    
    // Use the index for other operations
    if let Some(idx) = map.get_index_of("banana") {
        // Get both key and value by index
        let (key, value) = map.get_index(idx).unwrap();
        println!("Key: {}, Value: {}", key, value);  // Key: banana, Value: 2
    }
}

get_index_of returns Option<usize>—the position in insertion order.

The Return Type Difference

use indexmap::IndexMap;
 
fn return_types() {
    let mut map = IndexMap::new();
    map.insert("a", 1);
    map.insert("b", 2);
    map.insert("c", 3);
    
    // get: Returns value reference
    // Option<&V>
    let value: Option<&i32> = map.get("a");
    match value {
        Some(v) => println!("Value: {}", v),
        None => println!("Not found"),
    }
    
    // get_index_of: Returns index position
    // Option<usize>
    let position: Option<usize> = map.get_index_of("a");
    match position {
        Some(idx) => {
            println!("Position: {}", idx);
            // Now need to call get_index to get value
            let (k, v) = map.get_index(idx).unwrap();
            println!("Key: {}, Value: {}", k, v);
        }
        None => println!("Not found"),
    }
    
    // If you just need the value: use get
    // If you need position: use get_index_of
    // If you need both: get_index_of + get_index
}

get directly gives the value; get_index_of gives position that can be used for further operations.

Using Index for Additional Operations

use indexmap::IndexMap;
 
fn index_operations() {
    let mut map = IndexMap::new();
    map.insert("first", 1);
    map.insert("second", 2);
    map.insert("third", 3);
    
    // get_index_of enables position-based operations
    
    if let Some(idx) = map.get_index_of("second") {
        // Remove by index (preserves order of remaining)
        let (key, value) = map.remove_index(idx).unwrap();
        println!("Removed: {} -> {}", key, value);
        
        // Insert at specific position
        map.insert_before(idx, "inserted", 100);
        
        // Or after specific position
        // map.insert_after(idx, "after", 200);
    }
    
    // Swap positions
    // map.swap_indices(0, 2);
    
    // All these operations require knowing the index
    // get_index_of provides that index from a key
}

get_index_of enables positional mutations that get doesn't support.

Complexity Analysis

use indexmap::IndexMap;
 
fn complexity() {
    let mut map = IndexMap::new();
    for i in 0..1_000_000 {
        map.insert(format!("key{}", i), i);
    }
    
    // Both operations are O(1) average
    // But they have different constant factors
    
    // get:
    // 1. Hash the key
    // 2. Look up in hash table
    // 3. Return value reference
    
    // get_index_of:
    // 1. Hash the key
    // 2. Look up in hash table
    // 3. Return stored index
    
    // Both use the same hash table lookup
    // The difference is what's stored and returned
    
    // get_index is O(1) - direct array access
    // get_index_of is O(1) - hash table lookup
    
    // To get value from index:
    // map.get_index(idx) is O(1)
    
    // So: get_index_of + get_index = get + index lookup
    // If you need index AND value, use get_index_of
    // If you only need value, use get
}

Both are O(1), but get is simpler when you only need the value.

When to Use Each

use indexmap::IndexMap;
 
fn when_to_use() {
    let mut map = IndexMap::new();
    map.insert("a", 1);
    map.insert("b", 2);
    map.insert("c", 3);
    
    // Use get when:
    // 1. You only need the value
    let value = map.get("a");
    
    // 2. Checking existence
    if map.get("key").is_some() {
        println!("Key exists");
    }
    
    // 3. Getting mutable reference
    if let Some(v) = map.get_mut("a") {
        *v += 1;
    }
    
    // Use get_index_of when:
    // 1. You need the position
    if let Some(idx) = map.get_index_of("b") {
        println!("Position: {}", idx);
    }
    
    // 2. You need to modify position (remove, insert before/after)
    if let Some(idx) = map.get_index_of("b") {
        map.remove_index(idx);
    }
    
    // 3. You need both key and value from position
    if let Some(idx) = map.get_index_of("a") {
        let (k, v) = map.get_index(idx).unwrap();
        println!("Key: {}, Value: {}", k, v);
    }
    
    // 4. Iterating from a specific position
    if let Some(idx) = map.get_index_of("b") {
        for (_, (key, value)) in map.iter().enumerate() {
            // Use position information
        }
    }
}

Use get for value access; use get_index_of for positional operations.

Combination Pattern

use indexmap::IndexMap;
 
fn combination_pattern() {
    let mut map = IndexMap::new();
    map.insert("a", 1);
    map.insert("b", 2);
    map.insert("c", 3);
    
    // Common pattern: get_index_of + get_index
    // When you need key, value, and position
    
    fn find_entry(map: &IndexMap<&str, i32>, key: &str) -> Option<(usize, &str, i32)> {
        map.get_index_of(key).map(|idx| {
            let (k, v) = map.get_index(idx).unwrap();
            (idx, *k, *v)
        })
    }
    
    if let Some((idx, key, value)) = find_entry(&map, "b") {
        println!("Index: {}, Key: {}, Value: {}", idx, key, value);
    }
    
    // Alternative: just use iter and position
    // But that's O(n) instead of O(1)
    
    fn find_entry_slow(map: &IndexMap<&str, i32>, key: &str) -> Option<(usize, &str, i32)> {
        map.iter()
            .enumerate()
            .find(|(_, (k, _))| *k == key)
            .map(|(idx, (k, v))| (idx, *k, *v))
    }
    // This is O(n)!
}

get_index_of provides O(1) position lookup; iterating to find position is O(n).

Iteration and Position

use indexmap::IndexMap;
 
fn iteration_position() {
    let mut map = IndexMap::new();
    map.insert("first", 1);
    map.insert("second", 2);
    map.insert("third", 3);
    
    // IndexMap preserves insertion order
    // get_index_of tells you position in that order
    
    for (key, value) in &map {
        let idx = map.get_index_of(key).unwrap();
        println!("Index {}: {} -> {}", idx, key, value);
    }
    
    // This is efficient because get_index_of is O(1)
    // The position is stored in the hash table
    
    // Contrast with HashMap:
    // - No order preservation
    // - No position queries
    // - Can't remove "first" inserted element specifically
}

Index positions enable order-aware operations that regular HashMap doesn't support.

Removal Differences

use indexmap::IndexMap;
 
fn removal_operations() {
    let mut map = IndexMap::new();
    map.insert("a", 1);
    map.insert("b", 2);
    map.insert("c", 3);
    map.insert("d", 4);
    
    // Remove by key (like HashMap)
    let removed = map.remove("b");  // Returns Option<V>
    println!("Removed: {:?}", removed);  // Some(2)
    
    // After remove, order shifts:
    // "a" at index 0
    // "c" at index 1 (was 2)
    // "d" at index 2 (was 3)
    
    let mut map2 = IndexMap::new();
    map2.insert("a", 1);
    map2.insert("b", 2);
    map2.insert("c", 3);
    map2.insert("d", 4);
    
    // Remove by index (preserves remaining order)
    if let Some(idx) = map2.get_index_of("b") {
        let (key, value) = map2.remove_index(idx);  // Returns Option<(K, V)>
        println!("Removed by index: {} -> {}", key.unwrap(), value.unwrap());
    }
    
    // remove_index preserves order of remaining elements
    // It's implemented as a swap with last element + pop
    // This is O(1) but changes indices of swapped elements
    
    // shift_remove_index is O(n) but keeps all other indices stable
    // Use when you need stable indices after removal
}

get_index_of enables index-based removal which has different behavior than key-based removal.

Insert at Position

use indexmap::IndexMap;
 
fn insert_at_position() {
    let mut map = IndexMap::new();
    map.insert("a", 1);
    map.insert("c", 3);
    
    // Regular insert puts at end
    map.insert("d", 4);
    // Order: a, c, d
    
    // Insert at specific position
    if let Some(idx) = map.get_index_of("c") {
        map.insert_before(idx, "b", 2);
    }
    // Order: a, b, c, d
    
    // insert_before and insert_after require knowing the position
    // get_index_of provides that position
    
    // Regular insert always appends to end
    // insert_before/insert_after insert at specific positions
    
    // This is unique to IndexMap - HashMap has no concept of position
}

Position-based insertion requires get_index_of to find the insertion point.

Performance Characteristics

use indexmap::IndexMap;
 
fn performance_characteristics() {
    // Both get and get_index_of are O(1) average
    // But they have different constant factors
    
    // get:
    // - Hash key
    // - Probe hash table
    // - Return value pointer
    // Total: 1 hash + 1-2 memory accesses
    
    // get_index_of:
    // - Hash key
    // - Probe hash table
    // - Return stored index
    // Total: 1 hash + 1-2 memory accesses
    
    // get_index (from index):
    // - Direct array access
    // Total: 1 memory access
    
    // So get_index_of + get_index = 2 operations
    // While get = 1 operation
    
    // Use get if you only need value
    // Use get_index_of if you need position
    // Use combination if you need both
    
    let mut map = IndexMap::new();
    for i in 0..1000 {
        map.insert(i, i * 10);
    }
    
    // Scenario 1: Just need value
    // Use get (faster - 1 lookup)
    let _ = map.get(&500);
    
    // Scenario 2: Need position
    // Use get_index_of (only option)
    let _ = map.get_index_of(&500);
    
    // Scenario 3: Need both key and value from position
    // Use get_index_of + get_index (2 lookups)
    if let Some(idx) = map.get_index_of(&500) {
        let (k, v) = map.get_index(idx).unwrap();
    }
    
    // Scenario 4: Need key and value, don't need position
    // Just iterate with .iter()
    for (k, v) in &map {
        // Direct iteration, no lookups
    }
}

Choose based on what information you need: value, position, or both.

Comparison Table

use indexmap::IndexMap;
 
fn comparison_table() {
    // | Method | Input | Returns | Complexity | Use Case |
    // |--------|-------|---------|------------|----------|
    // | get | Key | Option<&V> | O(1) avg | Get value |
    // | get_mut | Key | Option<&mut V> | O(1) avg | Modify value |
    // | get_index_of | Key | Option<usize> | O(1) avg | Get position |
    // | get_index | usize | Option<(&K, &V)> | O(1) | Get key-value by position |
    // | get_index_mut | usize | Option<(&K, &mut V)> | O(1) | Modify by position |
    
    let mut map = IndexMap::new();
    map.insert("a", 1);
    map.insert("b", 2);
    
    // All these work together:
    
    // Key -> Value (direct)
    let v = map.get("a");
    
    // Key -> Index -> Key-Value (two step)
    if let Some(idx) = map.get_index_of("a") {
        let (k, v) = map.get_index(idx).unwrap();
    }
    
    // Index -> Key-Value (direct)
    let kv = map.get_index(0);
    
    // None of these have equivalents in HashMap
    // HashMap only has get (by key)
}

IndexMap provides both key-based and position-based access; HashMap only provides key-based.

Memory Layout

use indexmap::IndexMap;
 
fn memory_layout() {
    // IndexMap stores:
    // 1. Hash table (key -> index mapping)
    // 2. Dense array of (key, value) pairs
    
    // This is why:
    // - get_index_of is O(1): hash table stores indices
    // - get_index is O(1): direct array access
    // - get is O(1): hash table lookup + offset to value
    
    // The index is stored alongside the hash table entry
    // So get_index_of doesn't need to search the array
    
    // Memory overhead vs HashMap:
    // - HashMap: hash table + (key, value) entries
    // - IndexMap: hash table with indices + (key, value) array
    // Extra: indices in hash table entries
    
    // But benefits:
    // - Order preservation
    // - Position queries
    // - Index-based operations
    // - Efficient iteration (dense array)
}

The additional memory for indices enables position queries.

Practical Example: Ordered Processing

use indexmap::IndexMap;
 
fn ordered_processing() {
    let mut map = IndexMap::new();
    
    // Insert in priority order
    map.insert("critical", 1);
    map.insert("high", 2);
    map.insert("medium", 3);
    map.insert("low", 4);
    
    // Process in order (IndexMap preserves insertion order)
    for (key, value) in &map {
        println!("{}: {}", key, value);
    }
    
    // Find position of specific priority
    if let Some(idx) = map.get_index_of("medium") {
        // All priorities before this are higher
        // All priorities after are lower
        
        // Can check relative position
        if idx == 0 {
            println!("First priority");
        } else if idx == map.len() - 1 {
            println!("Last priority");
        } else {
            println!("Middle priority at index {}", idx);
        }
    }
    
    // This pattern is useful for:
    // - Ordered configuration
    // - Priority queues
    // - Dependency ordering
    // - Any ordered semantic structure
}

Order preservation combined with position queries enables ordered semantic processing.

Practical Example: Undo/Redo

use indexmap::IndexMap;
 
fn undo_redo_example() {
    // Undo system where you need to:
    // 1. Track operations in order
    // 2. Find position of specific operations
    // 3. Remove operations by position
    
    let mut operations: IndexMap<String, fn() -> ()> = IndexMap::new();
    
    // Register operations in order
    // operations.insert("op1", func1);
    // operations.insert("op2", func2);
    // operations.insert("op3", func3);
    
    // Find position of operation to undo
    // if let Some(idx) = operations.get_index_of("op2") {
    //     // Remove and undo
    //     operations.remove_index(idx);
    // }
    
    // Insert new operation at specific position
    // if let Some(idx) = operations.get_index_of("op1") {
    //     operations.insert_after(idx, "new_op", new_func);
    // }
    
    // Without get_index_of, you'd have to:
    // 1. Iterate to find position (O(n))
    // 2. Use two separate structures (HashMap + Vec)
}

get_index_of enables efficient position-based operations on an ordered map.

Synthesis

Quick reference:

Operation Method Complexity When to Use
Get value by key get O(1) Just need value
Get position of key get_index_of O(1) Need position
Get key-value by index get_index O(1) Have position, need entry
Remove by key remove O(1) Have key
Remove by position remove_index O(1) Have position

Decision guide:

// Need value only?
map.get("key");  // Use get
 
// Need position only?
map.get_index_of("key");  // Use get_index_of
 
// Need both key and value (but not position)?
map.iter().find(|(k, _)| *k == "key");  // Or use get + iter if just value
 
// Need position to modify order (remove at, insert before)?
if let Some(idx) = map.get_index_of("key") {
    map.remove_index(idx);
    // or
    map.insert_before(idx, "new_key", value);
}

Key insight: The trade-off isn't about performance—both get and get_index_of are O(1)—it's about what information you need. get returns the value directly, which is what you need most often. get_index_of returns the position, which enables all the position-aware operations that make IndexMap unique: removing by index, inserting at specific positions, swapping entries, and reasoning about order. The position is stored in the hash table, so get_index_of is essentially free once you've done the hash lookup. The common pattern is using get_index_of to find a position, then get_index, remove_index, or insert_before/after to operate on that position. This combination gives you ordered-map semantics—HashMap speed with Vec-like positional operations—that neither HashMap nor Vec alone provides.