diff --git a/traits_futures/traits_executor.py b/traits_futures/traits_executor.py
index 2cdb02d..04d3fd0 100644
--- a/traits_futures/traits_executor.py
+++ b/traits_futures/traits_executor.py
@@ -46,6 +46,20 @@ def _background_job_wrapper(background_job, sender):
 class TraitsExecutor(HasStrictTraits):
     """
     Executor to initiate and manage background tasks.
+
+    Parameters
+    ----------
+    thread_pool : concurrent.futures.ThreadPoolExecutor, optional
+        If supplied, provides the underlying thread pool executor to use. In
+        this case, the creator of the TraitsExecutor is responsible for
+        shutting down the thread pool once it's no longer needed. If not
+        supplied, a new private thread pool will be created, and this object's
+        ``stop`` method will shut down that thread pool.
+    max_workers : int or None, optional
+        Maximum number of workers for the private thread pool. This parameter
+        is mutually exclusive with thread_pool. The default is ``None``, which
+        delegates the choice of number of workers to Python's
+        ``ThreadPoolExecutor``.
     """
     #: Current state of this executor.
     state = ExecutorState
@@ -58,14 +72,18 @@ class TraitsExecutor(HasStrictTraits):
     #: to dispose of related resources (like the thread pool).
     stopped = Property(Bool())
 
-    def __init__(self, thread_pool=None, **traits):
+    def __init__(self, thread_pool=None, max_workers=None, **traits):
         super(TraitsExecutor, self).__init__(**traits)
 
         if thread_pool is None:
             self._thread_pool = concurrent.futures.ThreadPoolExecutor(
-                max_workers=4)
+                max_workers=max_workers)
             self._own_thread_pool = True
         else:
+            if max_workers is not None:
+                raise TypeError(
+                    "at most one of 'thread_pool' and 'max_workers' "
+                    "should be supplied")
             self._thread_pool = thread_pool
             self._own_thread_pool = False
 

Default number of workers in the `TraitsExecutor` should be configurable
We have a hard-coded `max_workers=4` where the `TraitsExecutor` creates its underlying `concurrent.futures.ThreadPoolExecutor`. That's wrong!

At the very least, that `4` should be a module-level constant, but it would be better to provide a way to create a `TraitsExecutor` that:

- owns its underlying executor
- allows the number of threads in that executor to be set in advance

diff --git a/traits_futures/tests/test_traits_executor.py b/traits_futures/tests/test_traits_executor.py
index 4a70743..1ad46d4 100644
--- a/traits_futures/tests/test_traits_executor.py
+++ b/traits_futures/tests/test_traits_executor.py
@@ -100,6 +100,17 @@ class TestTraitsExecutor(GuiTestAssistant, unittest.TestCase):
             del self.executor
         GuiTestAssistant.tearDown(self)
 
+    def test_max_workers(self):
+        executor = TraitsExecutor(max_workers=11)
+        self.assertEqual(executor._thread_pool._max_workers, 11)
+        executor.stop()
+        self.wait_until_stopped(executor)
+
+    def test_max_workers_mutually_exclusive_with_thread_pool(self):
+        with self.temporary_thread_pool() as thread_pool:
+            with self.assertRaises(TypeError):
+                TraitsExecutor(thread_pool=thread_pool, max_workers=11)
+
     def test_stop_method(self):
         executor = TraitsExecutor()
         listener = ExecutorListener(executor=executor)

pip install -e .[dev]
parse_log_pytest
requirements.txt
chaco coverage enable enthought_sphinx_theme numpy sphinx traitsui pytest
3.9
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.16
babel==2.17.0
certifi==2025.1.31
chaco==6.0.0
charset-normalizer==3.4.1
coverage==7.8.0
docutils==0.21.2
enable==6.0.0
enthought-sphinx-theme==0.7.3
exceptiongroup==1.2.2
fonttools==4.56.0
idna==3.10
imagesize==1.4.1
importlib_metadata==8.6.1
iniconfig==2.1.0
Jinja2==3.1.6
kiwisolver==1.4.7
MarkupSafe==3.0.2
numpy==2.0.2
packaging==24.2
pillow==11.1.0
pluggy==1.5.0
pyface==8.0.0
Pygments==2.19.1
pyparsing==3.2.3
pytest==8.3.5
requests==2.32.3
snowballstemmer==2.2.0
Sphinx==7.4.7
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
tomli==2.2.1
traits==7.0.2
-e git+https://github.com/enthought/traits-futures.git@90be7ad24df65405877602d35eb2c7e870457f6e#egg=traits_futures
traitsui==8.0.0
urllib3==2.3.0
zipp==3.21.0

name: traits-futures
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
  - 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=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.16
      - babel==2.17.0
      - certifi==2025.1.31
      - chaco==6.0.0
      - charset-normalizer==3.4.1
      - coverage==7.8.0
      - docutils==0.21.2
      - enable==6.0.0
      - enthought-sphinx-theme==0.7.3
      - exceptiongroup==1.2.2
      - fonttools==4.56.0
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - jinja2==3.1.6
      - kiwisolver==1.4.7
      - markupsafe==3.0.2
      - numpy==2.0.2
      - packaging==24.2
      - pillow==11.1.0
      - pluggy==1.5.0
      - pyface==8.0.0
      - pygments==2.19.1
      - pyparsing==3.2.3
      - pytest==8.3.5
      - requests==2.32.3
      - snowballstemmer==2.2.0
      - sphinx==7.4.7
      - sphinxcontrib-applehelp==2.0.0
      - sphinxcontrib-devhelp==2.0.0
      - sphinxcontrib-htmlhelp==2.1.0
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==2.0.0
      - sphinxcontrib-serializinghtml==2.0.0
      - tomli==2.2.1
      - traits==7.0.2
      - traitsui==8.0.0
      - urllib3==2.3.0
      - zipp==3.21.0
prefix: /opt/conda/envs/traits-futures

traits_futures/tests/test_traits_executor.py::TestTraitsExecutor::test_max_workers traits_futures/tests/test_traits_executor.py::TestTraitsExecutor::test_max_workers_mutually_exclusive_with_thread_pool
traits_futures/tests/test_traits_executor.py::test_call traits_futures/tests/test_traits_executor.py::TestTraitsExecutor::test_cant_submit_new_unless_running traits_futures/tests/test_traits_executor.py::TestTraitsExecutor::test_multiple_futures traits_futures/tests/test_traits_executor.py::TestTraitsExecutor::test_owned_thread_pool traits_futures/tests/test_traits_executor.py::TestTraitsExecutor::test_running traits_futures/tests/test_traits_executor.py::TestTraitsExecutor::test_running_and_stopped_fired_only_once traits_futures/tests/test_traits_executor.py::TestTraitsExecutor::test_running_and_stopped_fired_only_once_no_futures traits_futures/tests/test_traits_executor.py::TestTraitsExecutor::test_shared_thread_pool traits_futures/tests/test_traits_executor.py::TestTraitsExecutor::test_states_consistent traits_futures/tests/test_traits_executor.py::TestTraitsExecutor::test_stop_cancels_running_futures traits_futures/tests/test_traits_executor.py::TestTraitsExecutor::test_stop_method traits_futures/tests/test_traits_executor.py::TestTraitsExecutor::test_stop_method_raises_unless_running traits_futures/tests/test_traits_executor.py::TestTraitsExecutor::test_stop_method_with_no_jobs traits_futures/tests/test_traits_executor.py::TestTraitsExecutor::test_stop_with_multiple_futures traits_futures/tests/test_traits_executor.py::TestTraitsExecutor::test_stopped traits_futures/tests/test_traits_executor.py::TestTraitsExecutor::test_submit_call traits_futures/tests/test_traits_executor.py::TestTraitsExecutor::test_submit_iteration traits_futures/tests/test_traits_executor.py::TestTraitsExecutor::test_submit_progress
traits_futures/traits_executor.py
traits_futures/traits_executor.py