diff --git a/README.md b/README.md
index ef25c1e..e41acc2 100644
--- a/README.md
+++ b/README.md
@@ -111,7 +111,7 @@ For example, to implement a strategy which uses of the equations of your system
 ...         self.n_rows = A.shape[0]
 ...         self.row_index = None
 ...
-...     def select_row_index(self, xk):
+...     def _select_row_index(self, xk):
 ...         if self.row_index is None:
 ...             self.row_index = self.n_rows
 ...         self.row_index = (self.row_index - 1) % self.n_rows
diff --git a/setup.py b/setup.py
index d53d7c2..4334c63 100644
--- a/setup.py
+++ b/setup.py
@@ -8,7 +8,13 @@ from setuptools import find_packages, setup
 with open("README.md") as readme_file:
     readme = readme_file.read()
 
-test_requirements = ["pytest", "pytest-cov", "pytest-raises", "pytest-allclose"]
+test_requirements = [
+    "pytest",
+    "pytest-cov",
+    "pytest-raises",
+    "pytest-allclose",
+    "pytest-timeout",
+]
 
 docs_requirements = ["sphinx==1.8.5", "sphinx-rtd-theme", "m2r"]
 
diff --git a/src/kaczmarz/_abc.py b/src/kaczmarz/_abc.py
index bb65812..d655324 100644
--- a/src/kaczmarz/_abc.py
+++ b/src/kaczmarz/_abc.py
@@ -18,13 +18,14 @@ class Base(ABC):
         Tolerance for convergence, `norm(normalized_residual) <= tol`.
     maxiter : int or float, optional
         Maximum number of iterations.
+        Defaults to `log(tol^2)/log(1 - 1/(10*min(m, n)))`.
     callback : function, optional
         User-supplied function to call after each iteration.
         It is called as callback(xk), where xk is the current solution vector.
     """
 
     def __init__(
-        self, A, b, x0=None, tol=1e-5, maxiter=float("inf"), callback=None,
+        self, A, b, x0=None, tol=1e-5, maxiter=None, callback=None,
     ):
         row_norms = np.sqrt((A ** 2).sum(axis=1)).reshape(-1, 1)
         b = np.array(b)
@@ -42,6 +43,10 @@ class Base(ABC):
 
         self._x0 = np.array(x0, dtype="float64").ravel()
         self._tol = tol
+
+        if maxiter is None:
+            maxiter = 2 * np.log(tol) / np.log(1 - 1 / (10 * min(A.shape)))
+
         self._maxiter = maxiter
         if callback is None:
 
@@ -66,6 +71,37 @@ class Base(ABC):
         """
         return self._xk.copy().reshape(*self._iterate_shape)
 
+    @classmethod
+    def iterates(cls, *args, **kwargs):
+        """Get the Kaczmarz iterates.
+
+        TODO: Describe the invisible arguments.
+
+        Returns
+        -------
+        iterates : iterable((n,) or (n, 1) array)
+            An iterable of the Kaczmarz iterates.
+            The shapes will be inferred from the shape of `x0` if provided, or `b` otherwise.
+        """
+        return cls(*args, **kwargs)
+
+    @classmethod
+    def solve(cls, *args, **kwargs):
+        """Solve a linear system of equations using the Kaczmarz algorithm.
+
+        TODO: Describe the invisible arguments.
+
+        Returns
+        -------
+        x : (n,) or (n, 1) array
+            The solution to the system `Ax = b`.
+            The shape will be inferred from the shape of `x0` if provided, or `b` otherwise.
+        """
+        iterates = cls.iterates(*args, **kwargs)
+        for x in iterates:
+            pass
+        return x
+
     def __next__(self):
         """Perform an iteration of the Kaczmarz algorithm.
 
@@ -86,8 +122,8 @@ class Base(ABC):
             raise StopIteration
 
         self._k += 1
-        self._ik = self.select_row_index(self._xk)
-        self._xk = self.update_iterate(self._xk, self._ik)
+        self._ik = self._select_row_index(self._xk)
+        self._xk = self._update_iterate(self._xk, self._ik)
         self._callback(self.xk)
 
         return self.xk
@@ -96,7 +132,22 @@ class Base(ABC):
         """Iterator for iterates of the Kaczmarz algorithm."""
         return self
 
-    def update_iterate(self, xk, ik):
+    @abstractmethod
+    def _select_row_index(self, xk):
+        """Select a row to use for the next Kaczmarz update.
+
+        Parameters
+        ----------
+        xk : (n,) array
+            The current Kaczmarz iterate.
+
+        Returns
+        -------
+        ik : int
+            The index of the next row to use.
+        """
+
+    def _update_iterate(self, xk, ik):
         """Apply the Kaczmarz update.
 
         Parameters
@@ -140,49 +191,3 @@ class Base(ABC):
             return True
 
         return False
-
-    @classmethod
-    def iterates(cls, *args, **kwargs):
-        """Get the Kaczmarz iterates.
-
-        TODO: Describe the invisible arguments.
-
-        Returns
-        -------
-        iterates : iterable((n,) or (n, 1) array)
-            An iterable of the Kaczmarz iterates.
-            The shapes will be inferred from the shape of `x0` if provided, or `b` otherwise.
-        """
-        return cls(*args, **kwargs)
-
-    @classmethod
-    def solve(cls, *args, **kwargs):
-        """Solve a linear system of equations using the Kaczmarz algorithm.
-
-        TODO: Describe the invisible arguments.
-
-        Returns
-        -------
-        x : (n,) or (n, 1) array
-            The solution to the system `Ax = b`.
-            The shape will be inferred from the shape of `x0` if provided, or `b` otherwise.
-        """
-        iterates = cls.iterates(*args, **kwargs)
-        for x in iterates:
-            pass
-        return x
-
-    @abstractmethod
-    def select_row_index(self, xk):
-        """Select a row to use for the next Kaczmarz update.
-
-        Parameters
-        ----------
-        xk : (n,) array
-            The current Kaczmarz iterate.
-
-        Returns
-        -------
-        ik : int
-            The index of the next row to use.
-        """
diff --git a/src/kaczmarz/_variants.py b/src/kaczmarz/_variants.py
index 137ee80..a653cb8 100644
--- a/src/kaczmarz/_variants.py
+++ b/src/kaczmarz/_variants.py
@@ -21,7 +21,7 @@ class Cyclic(kaczmarz.Base):
         self.n_rows = A.shape[0]
         self.row_index = -1
 
-    def select_row_index(self, xk):
+    def _select_row_index(self, xk):
         """Get the next row index, in cyclic order.
 
         Parameters

Deal with inconsistent systems.
We have two options when the system of equations is inconsistent:
* Set a default finite value for `maxiter`.
* Raise a StopIteration whenever the error increases from one iteration to the next.
My preference is the former, since I frequently plot error vs iteration for inconsistent systems knowing that they will not converge.
diff --git a/tests/test_abc.py b/tests/test_abc.py
index 322fb6a..3438e9d 100644
--- a/tests/test_abc.py
+++ b/tests/test_abc.py
@@ -7,7 +7,7 @@ import kaczmarz
 @pytest.fixture()
 def DummyStrategy():
     class _DummyStrategy(kaczmarz.Base):
-        def select_row_index(self, xk):
+        def _select_row_index(self, xk):
             return 0
 
     return _DummyStrategy
@@ -37,6 +37,14 @@ def test_undefined_abstract_method(eye23, ones2, DummyStrategy, NonStrategy):
     DummyStrategy(eye23, ones2)
 
 
+@pytest.mark.timeout(1)
+def test_inconsistent_system_terminates(eye23, ones2, DummyStrategy, NonStrategy):
+    """Make sure inconsistent systems do not run forever."""
+    A = np.array([[1], [2]])
+    b = np.array([1, 1])
+    DummyStrategy.solve(A, b)
+
+
 def test_single_row_matrix(DummyStrategy, allclose):
     A = np.array([[0, 0, 1, 1]])
     b = np.array([1])
diff --git a/tests/test_cyclic.py b/tests/test_cyclic.py
index 591ef5d..63981f6 100644
--- a/tests/test_cyclic.py
+++ b/tests/test_cyclic.py
@@ -7,10 +7,10 @@ import kaczmarz
 def test_row_indexes(eye23, ones2):
     x = np.zeros(3)
     cyclic = kaczmarz.Cyclic(eye23, ones2)
-    assert 0 == cyclic.select_row_index(x)
-    assert 1 == cyclic.select_row_index(x)
-    assert 0 == cyclic.select_row_index(x)
-    assert 1 == cyclic.select_row_index(x)
+    assert 0 == cyclic._select_row_index(x)
+    assert 1 == cyclic._select_row_index(x)
+    assert 0 == cyclic._select_row_index(x)
+    assert 1 == cyclic._select_row_index(x)
 
     x0 = np.zeros(3)
     iterates = kaczmarz.Cyclic.iterates(eye23, ones2, x0)
@@ -28,7 +28,14 @@ def test_solve_identity(eye33, ones3):
 
 
 def test_solve_non_orthogonal_matrix():
-    A = np.array([[1, 0, 0], [2, 1, 0], [1, 2, 1], [0, 1, 2], [0, 0, 1]])
+    Alol = [
+        [1, 0, 0],
+        [2, 1, 0],
+        [1, 2, 1],
+        [0, 1, 2],
+        [0, 0, 1],
+    ]
+    A = np.array(Alol)
     row_norms = np.linalg.norm(A, axis=1)
     x_exact = np.ones(3)
     b = A @ x_exact

pip install -e .[dev]
parse_log_pytest
pytest
pytest
apt-get update apt-get install -y gcc
3.8
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
asttokens==3.0.0
babel==2.17.0
backcall==0.2.0
backports.tarfile==1.2.0
bump2version==1.0.1
cachetools==5.5.2
certifi==2025.1.31
cffi==1.17.1
cfgv==3.4.0
chardet==5.2.0
charset-normalizer==3.4.1
colorama==0.4.6
coverage==7.6.1
cryptography==44.0.2
decorator==5.2.1
distlib==0.3.9
docutils==0.18.1
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
executing==2.2.0
filelock==3.16.1
id==1.5.0
identify==2.6.1
idna==3.10
imagesize==1.4.1
importlib_metadata==8.5.0
importlib_resources==6.4.5
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
ipython==8.12.3
jaraco.classes==3.4.0
jaraco.context==6.0.1
jaraco.functools==4.1.0
jedi==0.19.2
jeepney==0.9.0
Jinja2==3.1.6
-e git+https://github.com/jdmoorman/kaczmarz-algorithms.git@87f809cef2e8abbca415542ad62bd642552eac01#egg=kaczmarz_algorithms
keyring==25.5.0
m2r==0.3.1
markdown-it-py==3.0.0
MarkupSafe==2.1.5
matplotlib-inline==0.1.7
mdurl==0.1.2
mistune==0.8.4
more-itertools==10.5.0
nh3==0.2.21
nodeenv==1.9.1
numpy==1.24.4
packaging==24.2
parso==0.8.4
pexpect==4.9.0
pickleshare==0.7.5
platformdirs==4.3.6
pluggy==1.5.0
pre-commit==3.5.0
prompt_toolkit==3.0.50
ptyprocess==0.7.0
pure_eval==0.2.3
pycparser==2.22
Pygments==2.19.1
pyproject-api==1.8.0
pytest @ file:///croot/pytest_1717793244625/work
pytest-allclose==1.0.0
pytest-cov==5.0.0
pytest-raises==0.11
pytest-runner==6.0.1
pytz==2025.2
PyYAML==6.0.2
readme_renderer==43.0
requests==2.32.3
requests-toolbelt==1.0.0
rfc3986==2.0.0
rich==14.0.0
scipy==1.10.1
SecretStorage==3.3.3
six==1.17.0
snowballstemmer==2.2.0
Sphinx==1.8.5
sphinx-rtd-theme==1.3.0
sphinxcontrib-jquery==4.1
sphinxcontrib-serializinghtml==1.1.5
sphinxcontrib-websupport==1.2.4
stack-data==0.6.3
tomli==2.2.1
tox==4.25.0
traitlets==5.14.3
twine==6.1.0
typing_extensions==4.13.0
urllib3==2.2.3
virtualenv==20.29.3
wcwidth==0.2.13
zipp==3.20.2

name: kaczmarz-algorithms
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py38h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=24.2=py38h06a4308_0
  - pytest=7.4.4=py38h06a4308_0
  - python=3.8.20=he870216_0
  - readline=8.2=h5eee18b_0
  - setuptools=75.1.0=py38h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.44.0=py38h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - asttokens==3.0.0
      - babel==2.17.0
      - backcall==0.2.0
      - backports-tarfile==1.2.0
      - bump2version==1.0.1
      - cachetools==5.5.2
      - certifi==2025.1.31
      - cffi==1.17.1
      - cfgv==3.4.0
      - chardet==5.2.0
      - charset-normalizer==3.4.1
      - colorama==0.4.6
      - coverage==7.6.1
      - cryptography==44.0.2
      - decorator==5.2.1
      - distlib==0.3.9
      - docutils==0.18.1
      - executing==2.2.0
      - filelock==3.16.1
      - id==1.5.0
      - identify==2.6.1
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==8.5.0
      - importlib-resources==6.4.5
      - ipython==8.12.3
      - jaraco-classes==3.4.0
      - jaraco-context==6.0.1
      - jaraco-functools==4.1.0
      - jedi==0.19.2
      - jeepney==0.9.0
      - jinja2==3.1.6
      - keyring==25.5.0
      - m2r==0.3.1
      - markdown-it-py==3.0.0
      - markupsafe==2.1.5
      - matplotlib-inline==0.1.7
      - mdurl==0.1.2
      - mistune==0.8.4
      - more-itertools==10.5.0
      - nh3==0.2.21
      - nodeenv==1.9.1
      - numpy==1.24.4
      - packaging==24.2
      - parso==0.8.4
      - pexpect==4.9.0
      - pickleshare==0.7.5
      - platformdirs==4.3.6
      - pluggy==1.5.0
      - pre-commit==3.5.0
      - prompt-toolkit==3.0.50
      - ptyprocess==0.7.0
      - pure-eval==0.2.3
      - pycparser==2.22
      - pygments==2.19.1
      - pyproject-api==1.8.0
      - pytest-allclose==1.0.0
      - pytest-cov==5.0.0
      - pytest-raises==0.11
      - pytest-runner==6.0.1
      - pytz==2025.2
      - pyyaml==6.0.2
      - readme-renderer==43.0
      - requests==2.32.3
      - requests-toolbelt==1.0.0
      - rfc3986==2.0.0
      - rich==14.0.0
      - scipy==1.10.1
      - secretstorage==3.3.3
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==1.8.5
      - sphinx-rtd-theme==1.3.0
      - sphinxcontrib-jquery==4.1
      - sphinxcontrib-serializinghtml==1.1.5
      - sphinxcontrib-websupport==1.2.4
      - stack-data==0.6.3
      - tomli==2.2.1
      - tox==4.25.0
      - traitlets==5.14.3
      - twine==6.1.0
      - typing-extensions==4.13.0
      - urllib3==2.2.3
      - virtualenv==20.29.3
      - wcwidth==0.2.13
      - zipp==3.20.2
prefix: /opt/conda/envs/kaczmarz-algorithms

tests/test_abc.py::test_undefined_abstract_method tests/test_abc.py::test_inconsistent_system_terminates tests/test_abc.py::test_single_row_matrix tests/test_abc.py::test_iterate_shape tests/test_abc.py::test_initial_guess tests/test_abc.py::test_ik tests/test_abc.py::test_maxiter tests/test_abc.py::test_solve tests/test_abc.py::test_tolerance tests/test_abc.py::test_callback tests/test_cyclic.py::test_row_indexes
tests/test_cyclic.py::test_solve_identity tests/test_cyclic.py::test_solve_non_orthogonal_matrix
setup.py src/kaczmarz/_variants.py README.md src/kaczmarz/_abc.py
setup.py src/kaczmarz/_variants.py README.md src/kaczmarz/_abc.py