Harnessing Julia’s Multiple Dispatch for Efficient Matrix Multiplication
Explore how Julia’s multiple dispatch lets you write clean, type‑safe matrix multiplication that compiles to fast machine code. See a step‑by‑step example, trade‑offs, and how to verify performance.
01 Apr 2026, 01:01 UTC

The Problem: Repeating Matrix Logic
When writing linear‑algebra routines, developers often copy‑paste loops for each numeric type. This leads to code bloat, duplicated logic, and a higher chance of bugs. In Julia, the language’s core feature—multiple dispatch—offers a clean alternative.
Multiple Dispatch 101
Multiple dispatch allows a single function name to have many implementations, each tailored to a specific combination of argument types. The compiler picks the best match at call time. Unlike inheritance‑based polymorphism, dispatch works with any type, including primitive numbers, custom structs, and arrays.
Key points:
- Method tables are built at runtime, so you can add or remove methods without recompiling callers.
- The compiler generates specialized machine code per type combination, often eliminating runtime type checks.
- Ambiguous signatures can cause compile‑time errors; use
methodsorwhichto inspect. - Type stability is essential; a method that returns
Anywill defeat specialization.
A Matrix Multiplication Example
Below we define a generic mul function that multiplies two matrices. We provide two type‑specific implementations: one for Array{Int,2} and another for Array{Float64,2}. The dispatch system picks the right one automatically.
# Generic fallback – uses element‑wise loops
function mul(A::AbstractArray, B::AbstractArray)
size(A,2) == size(B,1) || error("inner dimensions must match")
C = zeros(eltype(A), size(A,1), size(B,2))
for i in 1:size(A,1)
for j in 1:size(B,2)
for k in 1:size(A,2)
C[i,j] += A[i,k] * B[k,j]
end
end
end
return C
end
# Optimized Int implementation – uses Int loops
function mul(A::Array{Int,2}, B::Array{Int,2})
size(A,2) == size(B,1) || error("inner dimensions must match")
C = Array{Int,2}(undef, size(A,1), size(B,2))
for i in 1:size(A,1)
for j in 1:size(B,2)
sum = 0
for k in 1:size(A,2)
sum += A[i,k] * B[k,j]
end
C[i,j] = sum
end
end
return C
end
# Optimized Float64 implementation – uses BLAS via Base
function mul(A::Array{Float64,2}, B::Array{Float64,2})
return A * B # dispatches to highly‑optimized BLAS
end
# Inspect methods
println("Methods for mul:")
println(methods(mul))
Running methods(mul) will list the three implementations. When you call mul(A,B) with Int matrices, Julia picks the Int version; with Float64 matrices, it calls the BLAS‑backed version.
Performance Verification
To confirm that dispatch is working as intended, use the following diagnostics. All commands should be run in the Julia REPL with the script loaded.
- Compile‑time check:
@code_llvm mul(A,B)on a specific type pair. Inspect the LLVM IR for absence of type checks and for calls to BLAS. - Runtime timing:
@time mul(A,B)on large matrices (e.g., 1000×1000). The Int version should be slower than the BLAS one, but still faster than the generic fallback. - Method resolution:
which(mul, (typeof(A), typeof(B)))to ensure the expected method is selected.
Example check:
A = rand(Int, 1000, 1000)
B = rand(Int, 1000, 1000)
@time mul(A,B) # should use the Int implementation
Trade‑offs & Limitations
- Ambiguous Methods: If you add a method that overlaps an existing one (e.g.,
mul(A::Array{Int,2}, B::AbstractArray)), Julia will raise an ambiguity error. Usemethods(mul)to diagnose. - Compilation Overhead: Each unique type combination compiles a new method. With many small dispatch methods, compilation time and memory can grow.
- Type Instability: If a method returns
Anyor changes its return type, the compiler cannot generate specialized code, leading to a generic fallback. - Runtime Cost of Dispatch: In tight loops where the same method is called repeatedly, dispatch overhead is negligible after the first call due to caching.
Next Steps
To deepen your use of multiple dispatch:
- Define custom numeric types and write
mulmethods for them. Verify type stability withisconstandisdefined. - Explore parametric types:
mul(A::Array{T,2}, B::Array{T,2}) where Tto reduce code duplication. - Use
Base.@propagate_inboundsto eliminate bounds checks in inner loops. - Profile with
Profile.@profileto ensure that dispatch is not a bottleneck.
With careful method design and verification, Julia’s multiple dispatch becomes a powerful tool for writing clean, high‑performance numerical code.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.