Using vectors for performance improvement in Haskell
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.
Introduction
Haskell lists are elegant, but they are not the best structure for every performance-sensitive task. When you need fast indexing, better cache locality, or dense numeric storage, vectors are often a much better choice. The vector package gives you both immutable and mutable options while still fitting naturally into idiomatic Haskell.
Why Vectors Are Faster Than Lists in Many Cases
Lists are linked structures. That makes prepending cheap and recursion pleasant, but it also means indexing is linear and memory layout is scattered. A vector stores elements contiguously, which improves cache behavior and makes random access effectively constant time.
That matters for workloads such as:
- numeric loops
- dynamic programming tables
- repeated indexing into large datasets
- algorithms that traverse arrays many times
If your code frequently uses !! on a list, you are usually looking at an opportunity to switch to vectors.
Immutable Vectors for Dense Data
For read-mostly data, immutable vectors are the easiest starting point.
This example uses Data.Vector.Unboxed, which is ideal for primitive numeric types. Unboxed vectors avoid pointer indirection and store raw values directly, which usually improves both speed and memory use.
Mutable Vectors for In-Place Updates
When an algorithm needs many updates, a mutable vector inside ST or IO can avoid repeated allocation of new immutable structures.
The mutable work stays local, and the outside API remains pure. That is a common Haskell pattern when performance matters but you still want clean functional boundaries.
Boxed, Unboxed, and Storable Choices
Choosing the right vector type matters:
- '
Data.Vectoris boxed and works for any type' - '
Data.Vector.Unboxedis usually best for primitive values such asIntorDouble' - '
Data.Vector.Storableis useful when you need C-compatible memory layout'
If you use boxed vectors for numeric code by default, you may leave performance on the table. The unboxed version is often the real win in data-heavy code.
Fusion and How to Keep It Working
The vector package supports fusion, which can combine chained operations and avoid intermediate allocations. Code such as U.sum (U.map f vec) can compile into a tight loop instead of building a whole mapped vector first.
Fusion is powerful, but it is easier to lose than many people realize. Converting back and forth to lists, forcing unnecessary intermediate structures, or mixing APIs carelessly can stop the optimizer from helping you.
Common Pitfalls
The most common mistake is switching from lists to boxed vectors and expecting dramatic numeric speedups automatically. For numbers, unboxed vectors are often the more meaningful upgrade.
Another issue is overusing indexing in a way that still recreates vectors repeatedly. Vectors give you cheap reads, but immutable updates still allocate unless you switch to a mutable workflow inside ST or IO.
Be careful when benchmarking. Tiny examples can hide allocation behavior, fusion effects, and garbage collection costs. Use realistic inputs and benchmark the whole operation, not just one expression in isolation.
Finally, do not replace lists blindly. If your code mainly streams data once from left to right, a list or streaming library may still be the better abstraction. Vectors help most when the access pattern benefits from array semantics.
Summary
- Vectors improve indexing speed and memory locality compared with lists.
- Use immutable vectors for read-heavy code and mutable vectors for update-heavy inner loops.
- Prefer unboxed vectors for primitive numeric data when possible.
- Let fusion help you by keeping operations in the vector world.
- Choose vectors when your algorithm needs array-like access, not just because they sound faster.
Related reading
- Validation and Testing accuracy widely different
- Variadic nested loops
- Vectorizable implementation of complementary error function erfcf
- Vectorization of a function dependent on 2 arrays in numpy
- Vectorizing a gradient descent algorithm
- Very fast 3D distance check?
- Very High Memory Usage in .NET 4.0
- Very low GPU usage during training in Tensorflow

DSA Fundamentals
Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.