Fast element lookup for a functional languageHaskell
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
In Haskell, the default list type [a] is a linked list with O(n) lookup by index or value. For fast element lookup, Haskell provides several immutable data structures: Data.Map (balanced BST with O(log n) key lookup), Data.HashMap (hash-based with O(1) average lookup), Data.Set (O(log n) membership testing), Data.IntMap (specialized O(log n) for Int keys), and Data.Vector (O(1) indexed access). Choosing the right structure depends on whether you need key-value lookup, membership testing, or indexed access.
List Lookup (O(n) — Baseline)
Lists work for small collections but become a bottleneck for anything larger than a few hundred elements. Every lookup traverses the list from the beginning.
Data.Map (O(log n) Key-Value Lookup)
Data.Map.Strict is a balanced binary search tree (size-balanced tree). Keys must be Ord. It maintains sorted order and provides O(log n) for lookup, insert, and delete. Use Map.Strict over Map.Lazy to avoid space leaks from unevaluated thunks.
Data.HashMap (O(1) Average Lookup)
Data.HashMap from the unordered-containers package uses hash array mapped tries (HAMT). It requires Hashable and Eq on keys. For String and Text keys, it is faster than Data.Map for large collections.
Data.Set (O(log n) Membership)
Use Data.Set when you only need to check whether an element exists, without associated values. It is more memory-efficient than a Map with dummy values.
Data.IntMap (O(log n) for Int Keys)
Data.IntMap uses a Patricia trie internally and is significantly faster than Map Int for integer keys. The complexity is O(min(n, W)) where W is the bit width (64 on modern systems), which is effectively O(1) for practical purposes.
Data.Vector (O(1) Indexed Access)
Data.Vector provides O(1) indexed access (like arrays in imperative languages). Use it when you need fast random access by position rather than key-value lookup.
Comparison Table
| Structure | Lookup | Insert | Delete | Key Type | Ordered |
[a] (list) | O(n) | O(1) prepend | O(n) | Index/Value | Yes |
Data.Map | O(log n) | O(log n) | O(log n) | Ord | Yes |
Data.HashMap | O(1) avg | O(1) avg | O(1) avg | Hashable | No |
Data.Set | O(log n) | O(log n) | O(log n) | Ord | Yes |
Data.IntMap | O(log n)* | O(log n)* | O(log n)* | Int only | Yes |
Data.Vector | O(1) index | O(n) | O(n) | Index only | Yes |
* Effectively O(1) due to bounded key width.
Common Pitfalls
- Using lists for lookup-heavy code: Lists are linked — every lookup is O(n). Switching from
lookup key pairstoMap.lookup key mapreduces lookup from O(n) to O(log n). For large datasets, this is the single most impactful performance improvement. - Using
Data.Map.Lazyinstead ofData.Map.Strict: The lazy variant delays evaluation of values, accumulating thunks that consume memory. UseData.Map.Strict(andIntMap.Strict,HashMap.Strict) unless you specifically need lazy semantics. - Forgetting to use
foldl'(strict fold):foldl(without the prime) builds up a chain of unevaluated thunks, causing stack overflow on large collections. Always usefoldl'orfoldl'from the strict module for numeric accumulations. - Choosing
MapwhenIntMaporHashMapwould be faster:MaprequiresOrdand uses tree comparisons. If keys areInt,IntMapis 2-5x faster. If keys areStringorTextand order does not matter,HashMapis faster for large collections. - Using
Vectorfor frequent insertions:Data.Vectoris immutable — inserting an element requires copying the entire array (O(n)). UseData.Sequencefor O(log n) insertion at arbitrary positions, or use mutable vectors (Data.Vector.Mutable) insideSTorIO.
Summary
- Use
Data.Mapfor ordered key-value lookup withOrdkeys (O(log n)) - Use
Data.HashMapfor unordered key-value lookup withHashablekeys (O(1) average) - Use
Data.Setfor membership testing without associated values - Use
Data.IntMapforIntkeys — significantly faster thanMap Int - Use
Data.Vectorfor O(1) indexed random access (array-like behavior) - Always use the
Strictvariants of Map, IntMap, and HashMap to avoid space leaks

