Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path →
Matrix Exponentiation
Raise a matrix to the n-th power in O(log n) multiplications instead of n — the trick that computes the billionth Fibonacci number or counts length-n walks in a graph almost instantly. It’s fast exponentiation, lifted from numbers to matrices. Solve it in Python or TypeScript, with hidden tests.
The problem
Implement matrix_power(mat, power): given a square integer matrix mat (a list of rows) and a non-negative integer power, return mat raised to that power (matrix multiplication, not element-wise). power = 0 returns the identity matrix. Use binary (fast) exponentiation to do it in O(log power) matrix multiplies.
Your turn — write it
Edit the stub, hit Run (or ⌘/Ctrl + Enter), and watch the hidden tests. Stuck? open the hints under the problem, or Reveal solution is one click away.
Implement matrix_power(mat, power): write a helper that multiplies two n×n matrices, start the result at the identity, then loop — if the low bit of power is set, multiply the result by the current base; then square the base and shift power right.
Examples and constraints 4 examples
mat = [[1,1],[1,0]], power = 10[[89, 55], [55, 34]]mat = [[2,0],[0,2]], power = 3[[8, 0], [0, 8]]mat = [[1,2],[3,4]], power = 3[[37, 54], [81, 118]]mat = [[5,7],[2,3]], power = 0[[1, 0], [0, 1]]- The matrix is square (n×n) with integer entries; use exact integer arithmetic.
power = 0→ the identity matrix (1 on the diagonal, 0 elsewhere).- Use fast exponentiation: square the base and halve the exponent, multiplying into the result when the current bit is set — O(log power) multiplies.
Hints 4 ideas
- First write
matmul(A, B): entry[i][j]is the dot product of rowiof A with columnjof B. - Initialise the result as the identity matrix so that "multiply into result" starts correctly.
- Fast exponentiation: while
power > 0, ifpoweris odd multiplyresult *= base; thenbase *= baseandpower //= 2. - This turns n multiplications into about log₂(n) — the same idea as fast exponentiation of plain numbers.
Approach, complexity & discussion — open after you solve
Start from brute force
The obvious approach starts from the identity and multiplies by mat once per unit of power. It is correct, since M^n is by definition n copies of M multiplied together. It costs O(k³ · power) time for a k×k matrix, because each schoolbook multiply is k³ operations, and O(k²) space for the running product. The multiply count grows linearly with the exponent: power = 10⁹ means a billion matrix multiplies, while repeated squaring needs about 60. That linear growth is exactly what the problems built on this trick, such as F(10¹⁸) mod a prime, are designed to rule out.
The approach
Write the exponent in binary: M^10 = M^8 · M^2 because 10 is 1010. Repeated squaring yields M, M², M⁴, M⁸, … at one multiply each, and the answer is the product of exactly the powers whose bit is set. The reference keeps result, starting at the identity (the matrix version of 1), and base, a copy of mat. Each iteration tests the low bit with power & 1 and, if it is set, does result = matmul(result, base); then base = matmul(base, base) and power >>= 1.
The invariant: result · base^power always equals the original matrix to the original power. It holds at the start, since the identity times mat^power is the answer, and both branches preserve it, so when power reaches 0, result is the answer. matmul builds entry [i][j] as the dot product of row i of A with column j of B.
Walk through an example
For the first example, power = 10 is 1010 in binary, so result picks up M² at step 2 and M⁸ at step 4, and the last squaring is computed but never used.
| step | power (binary) | low bit | result after | base after squaring |
|---|---|---|---|---|
| 1 | 10 (1010) | 0 | I = [[1,0],[0,1]] | M² = [[2,1],[1,1]] |
| 2 | 5 (101) | 1 | I·M² = [[2,1],[1,1]] | M⁴ = [[5,3],[3,2]] |
| 3 | 2 (10) | 0 | M² = [[2,1],[1,1]] | M⁸ = [[34,21],[21,13]] |
| 4 | 1 (1) | 1 | M²·M⁸ = [[89,55],[55,34]] | M¹⁶ = [[1597,987],[987,610]], unused |
Complexity
The loop runs ⌊log₂ power⌋ + 1 times, with one squaring each plus one multiply per set bit, so time is O(k³ log power) for a k×k matrix and space is O(k²) for result, base and one product. Repeated multiplication is O(k³ · power) time with the same O(k²) space. Big-integer entries make each multiply slower as numbers grow.
Edge cases to test
power = 0returns the identity, even for the zero matrix:matrix_power([[0,0],[0,0]], 0)gives[[1, 0], [0, 1]], by the convention M⁰ = I.power = 1returns the input unchanged:matrix_power([[1,2],[3,4]], 1)gives[[1, 2], [3, 4]].- A 1×1 matrix is ordinary fast exponentiation:
matrix_power([[3]], 4)returns[[81]]. - A negative power is not rejected: the loop never runs, so
matrix_power([[1,1],[1,0]], -1)silently returns the identity. Validate the input; a true inverse needs fractions or a modulus. - Large entries: Python integers are exact at any size, so
matrix_power([[1,1],[1,0]], 90)[0][1]is exactly F(90) = 2880067194370816120. TypeScript numbers are 64-bit floats, exact only up to 2⁵³ − 1, so at power 78 the top-left entry, F(79) = 14472334024676221, already comes back as 14472334024676220.
Common mistakes
- Naive repeated multiplication O(n) instead of the O(log n) squaring.
- Wrong base case — the accumulator must start as the identity matrix.
- Losing exactness on large entries. Python integers never overflow, but every TypeScript number is a 64-bit float, exact only up to 2⁵³ − 1; use BigInt, or reduce by a modulus after every multiply when the problem asks for one.
- Raising each entry to the power instead of multiplying matrices: for
[[1,2],[3,4]]cubed that gives[[1, 8], [27, 64]]instead of[[37, 54], [81, 118]].
Follow-ups an interviewer asks next
- Compute the n-th Fibonacci number mod 10⁹ + 7 for n up to 10¹⁸.
- Raise
[[1,1],[1,0]]to the n-th power, reducing every entry mod1_000_000_007after each multiply, and read[0][1], which is F(n). Since log₂(10¹⁸) is about 60, that is at most about 120 two-by-two multiplies. In TypeScript use BigInt for the products: two numbers near 10⁹ multiply to about 10¹⁸, past 2⁵³. - How does this solve a general linear recurrence?
- Put the last k terms in a state vector and write one step as a k×k companion matrix. For
a(n) = 2·a(n−1) + 3·a(n−2)the matrix is[[2,3],[1,0]], and[a(n), a(n−1)] = M^(n−1) · [a(1), a(0)]. Any order-k recurrence with constant coefficients then costs O(k³ log n). - What does a power of an adjacency matrix count?
- Entry
[i][j]ofA^ncounts the walks of exactly n edges from i to j, because each multiply extends every walk by one edge. For the graph with edges 0→1, 0→2, 1→2 and 2→0,A³has a 1 at[0][0]: the single closed walk 0 → 1 → 2 → 0. - Can each multiply be faster than k³?
- Strassen’s algorithm multiplies in about O(k^2.81) by trading multiplications for additions, and it pays off only for large k. For the small matrices in recurrence problems, the practical wins are a modulus, an unrolled 2×2 multiply, and skipping the final squaring, which the reference computes and never uses.
Where this shows up
Matrix exponentiation evaluates linear recurrences in O(log n) — the classic [[1,1],[1,0]]^n computes Fibonacci fast. It also counts length-n paths in a graph (powers of the adjacency matrix) and advances Markov chains, making it a go-to for “huge n” recurrence and counting problems.
Reference solution
Reference solution — Python
def matrix_power(mat, power):
n = len(mat)
def matmul(A, B):
return [[sum(A[i][k] * B[k][j] for k in range(n)) for j in range(n)]
for i in range(n)]
# start at the identity matrix
result = [[1 if i == j else 0 for j in range(n)] for i in range(n)]
base = [row[:] for row in mat]
while power > 0:
if power & 1:
result = matmul(result, base) # multiply in when the bit is set
base = matmul(base, base) # square the base
power >>= 1 # halve the exponent
return result
print(matrix_power([[1, 1], [1, 0]], 10)) # -> [[89, 55], [55, 34]]
print(matrix_power([[2, 0], [0, 2]], 3)) # -> [[8, 0], [0, 8]]Reference solution — TypeScript
function matrixPower(mat: number[][], power: number): number[][] {
const n = mat.length;
const matmul = (A: number[][], B: number[][]): number[][] =>
Array.from({ length: n }, (_, i) =>
Array.from({ length: n }, (_, j) => {
let s = 0;
for (let k = 0; k < n; k++) s += A[i][k] * B[k][j];
return s;
}));
// identity matrix
let result = Array.from({ length: n }, (_, i) =>
Array.from({ length: n }, (_, j) => (i === j ? 1 : 0)));
let base = mat.map((row) => row.slice());
while (power > 0) {
if (power & 1) result = matmul(result, base); // multiply in on a set bit
base = matmul(base, base); // square the base
power = Math.floor(power / 2); // halve the exponent
}
return result;
}
console.log(JSON.stringify(matrixPower([[1,1],[1,0]], 10))); // -> [[89,55],[55,34]]
console.log(JSON.stringify(matrixPower([[2,0],[0,2]], 3))); // -> [[8,0],[0,8]]Questions about Matrix Exponentiation
How does fast matrix exponentiation work?
It writes the exponent in binary. Squaring a matrix repeatedly gives M, M squared, M to the 4th, M to the 8th and so on, one multiply each, and any power is a product of some of them: M to the 10th is M to the 8th times M squared, because 10 is 1010 in binary. The algorithm keeps a running result that starts at the identity, multiplies the current square into it whenever the lowest bit of the exponent is 1, then squares and halves the exponent. That takes about log2 of the power iterations instead of the power itself.
Why does the result start as the identity matrix?
The identity plays the role that 1 plays for numbers: multiplying any matrix by it changes nothing. Starting the result there means the first multiply simply brings in the first power that is needed, and it gives the right answer when the exponent is 0, because any matrix to the power 0 is defined as the identity. Starting from the input matrix instead counts one factor too many, and starting from the zero matrix makes every product zero. The identity has 1 on the diagonal and 0 everywhere else.
How do you compute Fibonacci numbers with matrix exponentiation?
The matrix with rows 1, 1 and 1, 0 advances a pair of consecutive Fibonacci numbers by one step, so its n-th power holds F(n+1) and F(n) in the top row and F(n) and F(n-1) in the bottom row. Raising it to the 10th power gives 89, 55, 55 and 34, the first example. With fast exponentiation F(n) costs about 2 log2 n two-by-two multiplies, so even an enormous n is cheap, usually with every entry reduced modulo a prime to keep the numbers small.
What is the time complexity of matrix exponentiation?
For a k by k matrix, one schoolbook multiply costs O(k cubed), and fast exponentiation does about 2 log2 n multiplies, so the total is O(k cubed log n) time and O(k squared) space. Multiplying by the matrix n times instead costs O(k cubed n). For the 2 by 2 Fibonacci matrix the k cubed factor is a constant, so the cost is O(log n) multiplies, although with exact big integers each multiply itself gets slower as the numbers grow.
Explore the topic
See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.
More Challenges
- Two SumThe classic warm-up: find the two numbers that add up to a target. Brute force is O(n²) — a hash map gets you to one pass, O(n). Solve it in Python or TypeScript, right in your browser, with hidden tests and a reveal-solution button.Read →
- Valid ParenthesesThe canonical stack problem: decide whether every bracket is closed by the right type, in the right order. A stack turns nested matching into a single pass. Solve it in Python or TypeScript with hidden tests.Read →
- Fizz BuzzThe famous screening question. Print 1…n, but multiples of 3 become "Fizz", of 5 become "Buzz", and of both become "FizzBuzz". Easy — the catch is testing divisibility in the right order. Solve it in Python or TypeScript.Read →
- Number of IslandsThe canonical connected-components question. Flood-fill each unvisited patch of land with BFS or DFS, count how many floods you started — grid traversal, visited bookkeeping, and the classic diagonal trap. Solve it in Python or TypeScript.Read →
- Merge IntervalsCalendar apps and memory allocators run on this: sort by start, sweep once, grow or close the current block. Touching endpoints merge, nested intervals vanish — the classic that punishes off-by-one thinking. Solve it in Python or TypeScript.Read →
- Top-K Frequent ElementsCount, then rank — the two-step pattern behind trending topics and hot-key detection. A hash map counts; a (-count, value) sort ranks with a clean tie-break; buckets get you to O(n) if you want the flex. Solve it in Python or TypeScript.Read →