According to the Bolzano theorem (see Wiki), a continuous function has a root in an interval, if it has values of opposite signs inside that interval.

Let’s assume that $f(a)>0$ and $ f(b)<0 $. Then there must be a root in $ (a,b) $.

Now let $ c=\frac{b+a}{2} $. If $ f(c) > 0 $ then $ c $ is an improvement of the left-hand bound, and if $ f(c) < 0 $ is an improvement over the right-hand side bound.

This process is repeated after a certain error tolerance is met.

```
function bisection(f, a, b, tol)
if sign(f(a)) == sign(f(b))
error("function has the same sign at given endpoints")
end
mid = (a + b)/2
while abs(f(mid)) > tol
sign(f(mid)) == sign(f(a)) ? a=mid : b=mid
mid = (a + b)/2
end
return mid
end
```

Let’s test our code in a simple example, like solving for $ x^2 - 2 = 0 $.

```
x = bisection( x -> x^2-2, 1, 2, 1e-5)
err = abs(x - sqrt(2))
print(err)
```

```
1.5255175298545254e-6
```

## Final remarks

The algorithm written above is a straightforward implementation of the bisection algorithm. For improved performance, however, there are a few things we could do.

For example, we notice that the code evaluates the function $ f $ more than once at the same point `mid`

. Precomputing the value once, by doing something like `fmid = f(mid)`

, and then relying on `fmid`

is a performance optimization called *memoization*.

The bisection algorithm, in spite of being the simplest root-finding algorithm, is very robust, because convergence is guaranteed when very basic conditions hold. For this reason, it is used as the basis of more advanced algorithms, that will also converge to a root much faster.