diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 510eba806fc6f27ed09dcee146feb931fed0c3de..ad24891431c1321e10c6abf55b006993ce493f56 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -34,6 +34,15 @@ Flake8:
   except:
   - tags
 
+Mypy:
+  script:
+  - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/prepare-and-run-mypy.sh
+  - ". ./prepare-and-run-mypy.sh python3 mypy"
+  tags:
+  - python3
+  except:
+  - tags
+
 Documentation:
   script:
   - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-docs.sh
diff --git a/pytato/array.py b/pytato/array.py
index b2eefd1a7c2866ac1f342cea53815635192eeadc..1f6f268bb07604940772f9719f95769b5112293c 100644
--- a/pytato/array.py
+++ b/pytato/array.py
@@ -156,11 +156,11 @@ class Array(ptype.ArrayInterface):
         purposefully so.
     """
 
-    def __init__(self, namespace: Namespace,
+    def __init__(self, namespace: ptype.NamespaceInterface,
                  name: Optional[ptype.NameType],
                  tags: Optional[ptype.TagsType] = None):
-        assert ptype.check_name(name)
-        assert ptype.check_tags(tags)
+        assert (name is None) or ptype.check_name(name)
+        assert (tags is None) or ptype.check_tags(tags)
 
         if tags is None:
             tags = {}
@@ -195,7 +195,7 @@ class Array(ptype.ArrayInterface):
             pass
 
     def without_tag(self, dotted_name: DottedName):
-        pass
+        raise NotImplementedError
 
     def with_name(self, name: ptype.NameType):
         assert ptype.check_name(name)
@@ -235,8 +235,7 @@ class DictOfNamedArrays(collections.abc.Mapping):
     def namespace(self):
         return single_valued(ary.namespace for ary in self._data.values())
 
-    def __contains__(self, name: ptype.NameType):
-        assert ptype.check_name(name)
+    def __contains__(self, name: object):
         return name in self._data
 
     def __getitem__(self, name: ptype.NameType):
diff --git a/pytato/typing.py b/pytato/typing.py
index 27f07f5b712e9edd19ec8b8ee5737d55520a8b37..fbc94c5ac705322b6692efdf2b6ba3ec3184dd6d 100644
--- a/pytato/typing.py
+++ b/pytato/typing.py
@@ -26,11 +26,12 @@ THE SOFTWARE.
 
 __doc__ = """Interface classes and type specifications.
 Each type is paired with a check_* function that, when used together, achieves
-contracts-like functionality."""
+contracts-like functionality.
+"""
 
 import re
 from pymbolic.primitives import Expression
-from abc import ABC
+from abc import ABC, abstractmethod
 from typing import Optional, Union, Dict, Tuple
 
 # {{{ abstract classes
@@ -39,6 +40,10 @@ from typing import Optional, Union, Dict, Tuple
 class NamespaceInterface():
     __metaclass__ = ABC
 
+    @abstractmethod
+    def assign(self, name, value):
+        pass
+
 
 class TagInterface():
     __metaclass__ = ABC
@@ -49,6 +54,40 @@ class ArrayInterface():
     """
     __metaclass__ = ABC
 
+    @property
+    def namespace(self):
+        return self._namespace
+
+    @namespace.setter
+    def namespace(self, val: NamespaceInterface):
+        self._namespace = val
+
+    @property
+    @abstractmethod
+    def ndim(self):
+        pass
+
+    @property
+    @abstractmethod
+    def shape(self):
+        pass
+
+    @abstractmethod
+    def copy(self, **kwargs):
+        pass
+
+    @abstractmethod
+    def with_tag(self, tag_key, tag_val):
+        pass
+
+    @abstractmethod
+    def without_tag(self, tag_key):
+        pass
+
+    @abstractmethod
+    def with_name(self, name):
+        pass
+
 # }}} End abstract classes
 
 # {{{ name type
@@ -91,7 +130,7 @@ def check_shape(shape: ShapeType,
             assert s > 0, f"size parameter must be positive (got {s})"
         elif isinstance(s, str):
             assert check_name(s)
-        elif isinstance(Expression) and ns is not None:
+        elif isinstance(s, Expression) and ns is not None:
             # TODO: check expression in namespace
             pass
     return True
diff --git a/run-mypy.sh b/run-mypy.sh
new file mode 100644
index 0000000000000000000000000000000000000000..14730b1d67342861db2c2e0d61655cbec4b0fdf0
--- /dev/null
+++ b/run-mypy.sh
@@ -0,0 +1,3 @@
+#! /bin/bash
+
+mypy --strict pytato
diff --git a/setup.cfg b/setup.cfg
index 07d57430d3bb0967fc8310fd40fb8ad3640e73bd..a8663c0804824e4362183ced8a8a576a5356e269 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,3 +1,9 @@
 [flake8]
 ignore = E126,E127,E128,E123,E226,E241,E242,E265,N802,W503,E402,N814,N817,W504
 max-line-length=85
+
+[mypy-pymbolic.primitives]
+ignore_missing_imports = True
+
+[mypy-pytools]
+ignore_missing_imports = True