diff --git a/src/language/modules/MeshModule.cpp b/src/language/modules/MeshModule.cpp
index 1f2d0ce4db829694dca9875593f168ebcac47b6a..9f709e2fd5cf70f31fa491ae8cd62fbe0986022f 100644
--- a/src/language/modules/MeshModule.cpp
+++ b/src/language/modules/MeshModule.cpp
@@ -24,8 +24,10 @@
 #include <mesh/MeshRelaxer.hpp>
 #include <mesh/MeshTransformer.hpp>
 #include <mesh/NamedBoundaryDescriptor.hpp>
+#include <mesh/NamedInterfaceDescriptor.hpp>
 #include <mesh/NamedZoneDescriptor.hpp>
 #include <mesh/NumberedBoundaryDescriptor.hpp>
+#include <mesh/NumberedInterfaceDescriptor.hpp>
 #include <mesh/NumberedZoneDescriptor.hpp>
 #include <mesh/SubItemArrayPerItemVariant.hpp>
 #include <mesh/SubItemValuePerItemVariant.hpp>
@@ -37,6 +39,7 @@ MeshModule::MeshModule()
 {
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IMesh>>);
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IBoundaryDescriptor>>);
+  this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IInterfaceDescriptor>>);
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IZoneDescriptor>>);
 
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const ItemType>>);
@@ -96,6 +99,31 @@ MeshModule::MeshModule()
 
                               ));
 
+  this->_addBuiltinFunction("boundaryTag", std::function(
+
+                                             [](int64_t boundary_tag) -> std::shared_ptr<const IBoundaryDescriptor> {
+                                               return std::make_shared<NumberedBoundaryDescriptor>(boundary_tag);
+                                             }
+
+                                             ));
+
+  this->_addBuiltinFunction("interfaceName",
+                            std::function(
+
+                              [](const std::string& interface_name) -> std::shared_ptr<const IInterfaceDescriptor> {
+                                return std::make_shared<NamedInterfaceDescriptor>(interface_name);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("interfaceTag", std::function(
+
+                                              [](int64_t interface_tag) -> std::shared_ptr<const IInterfaceDescriptor> {
+                                                return std::make_shared<NumberedInterfaceDescriptor>(interface_tag);
+                                              }
+
+                                              ));
+
   this->_addBuiltinFunction("zoneTag", std::function(
 
                                          [](int64_t zone_tag) -> std::shared_ptr<const IZoneDescriptor> {
@@ -112,14 +140,6 @@ MeshModule::MeshModule()
 
                                           ));
 
-  this->_addBuiltinFunction("boundaryTag", std::function(
-
-                                             [](int64_t boundary_tag) -> std::shared_ptr<const IBoundaryDescriptor> {
-                                               return std::make_shared<NumberedBoundaryDescriptor>(boundary_tag);
-                                             }
-
-                                             ));
-
   this->_addBuiltinFunction("interpolate",
                             std::function(
 
diff --git a/src/language/modules/MeshModule.hpp b/src/language/modules/MeshModule.hpp
index 580f25aa839e2c1cc691247dd7359d8a6c1aa036..0ea7e5762f164734527aec0e62f7f70967bef01b 100644
--- a/src/language/modules/MeshModule.hpp
+++ b/src/language/modules/MeshModule.hpp
@@ -15,6 +15,11 @@ template <>
 inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IBoundaryDescriptor>> =
   ASTNodeDataType::build<ASTNodeDataType::type_id_t>("boundary");
 
+class IInterfaceDescriptor;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IInterfaceDescriptor>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("interface");
+
 class IZoneDescriptor;
 template <>
 inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IZoneDescriptor>> =
diff --git a/src/mesh/CMakeLists.txt b/src/mesh/CMakeLists.txt
index 39f31282296315f1dea4647bcb9d02aa43169707..ee69a9710d8706072e1fbd80eb51a6d3e0dd9b5c 100644
--- a/src/mesh/CMakeLists.txt
+++ b/src/mesh/CMakeLists.txt
@@ -25,7 +25,9 @@ add_library(
   MeshData.cpp
   MeshDataManager.cpp
   MeshEdgeBoundary.cpp
+  MeshEdgeInterface.cpp
   MeshFaceBoundary.cpp
+  MeshFaceInterface.cpp
   MeshFlatEdgeBoundary.cpp
   MeshFlatFaceBoundary.cpp
   MeshFlatNodeBoundary.cpp
@@ -34,6 +36,7 @@ add_library(
   MeshLineFaceBoundary.cpp
   MeshLineNodeBoundary.cpp
   MeshNodeBoundary.cpp
+  MeshNodeInterface.cpp
   MeshRandomizer.cpp
   MeshSmoother.cpp
   MeshTransformer.cpp
diff --git a/src/mesh/Connectivity.cpp b/src/mesh/Connectivity.cpp
index dc03c1ee65224a07466e5f08352b53004ad3c1ea..27850821bf64eb590915bbdccce8e8b365fc0eb4 100644
--- a/src/mesh/Connectivity.cpp
+++ b/src/mesh/Connectivity.cpp
@@ -100,8 +100,8 @@ Connectivity<Dimension>::_buildFrom(const ConnectivityDescriptor& descriptor)
         face_list[i] = FaceId::base_type{node_list[i]};
       }
 
-      m_ref_edge_list_vector.emplace_back(RefItemList<ItemType::edge>(ref_id, edge_list, ref_node_list.isBoundary()));
-      m_ref_face_list_vector.emplace_back(RefItemList<ItemType::face>(ref_id, face_list, ref_node_list.isBoundary()));
+      m_ref_edge_list_vector.emplace_back(RefItemList<ItemType::edge>(ref_id, edge_list, ref_node_list.type()));
+      m_ref_face_list_vector.emplace_back(RefItemList<ItemType::face>(ref_id, face_list, ref_node_list.type()));
     }
 
   } else {
@@ -142,7 +142,7 @@ Connectivity<Dimension>::_buildFrom(const ConnectivityDescriptor& descriptor)
           edge_list[i] = EdgeId::base_type{face_list[i]};
         }
 
-        m_ref_edge_list_vector.emplace_back(RefItemList<ItemType::edge>(ref_id, edge_list, ref_face_list.isBoundary()));
+        m_ref_edge_list_vector.emplace_back(RefItemList<ItemType::edge>(ref_id, edge_list, ref_face_list.type()));
       }
 
     } else {
diff --git a/src/mesh/ConnectivityDispatcher.cpp b/src/mesh/ConnectivityDispatcher.cpp
index 641d7f8666946a448d98e0a3311d5bd2a94329fc..797ac6cff42c5a1077686f28515793f990b6218a 100644
--- a/src/mesh/ConnectivityDispatcher.cpp
+++ b/src/mesh/ConnectivityDispatcher.cpp
@@ -465,15 +465,15 @@ ConnectivityDispatcher<Dimension>::_buildItemReferenceList()
       Assert(number_of_item_list_sender < parallel::size());
 
       // sending is boundary property
-      Array<bool> ref_item_list_is_boundary{number_of_item_ref_list_per_proc[sender_rank]};
+      Array<RefItemListBase::Type> ref_item_list_type{number_of_item_ref_list_per_proc[sender_rank]};
       if (parallel::rank() == sender_rank) {
         for (size_t i_item_ref_list = 0; i_item_ref_list < m_connectivity.template numberOfRefItemList<item_type>();
              ++i_item_ref_list) {
-          auto item_ref_list                         = m_connectivity.template refItemList<item_type>(i_item_ref_list);
-          ref_item_list_is_boundary[i_item_ref_list] = item_ref_list.isBoundary();
+          auto item_ref_list                  = m_connectivity.template refItemList<item_type>(i_item_ref_list);
+          ref_item_list_type[i_item_ref_list] = item_ref_list.type();
         }
       }
-      parallel::broadcast(ref_item_list_is_boundary, sender_rank);
+      parallel::broadcast(ref_item_list_type, sender_rank);
 
       // sending references tags
       Array<RefId::TagNumberType> ref_tag_list{number_of_item_ref_list_per_proc[sender_rank]};
@@ -593,8 +593,8 @@ ConnectivityDispatcher<Dimension>::_buildItemReferenceList()
 
           Array<const ItemId> item_id_array = convert_to_array(item_id_vector);
 
-          bool is_boundary = ref_item_list_is_boundary[i_ref];
-          m_new_descriptor.addRefItemList(RefItemList<item_type>(ref_id_list[i_ref], item_id_array, is_boundary));
+          RefItemListBase::Type type = ref_item_list_type[i_ref];
+          m_new_descriptor.addRefItemList(RefItemList<item_type>(ref_id_list[i_ref], item_id_array, type));
         }
       }
     }
diff --git a/src/mesh/DiamondDualConnectivityBuilder.cpp b/src/mesh/DiamondDualConnectivityBuilder.cpp
index c6a8fd78a9774dc63cbc5e5398ce721c5cfb9acb..2cc1f47cd3be15c1012dda01c0c1c5b84ae66355 100644
--- a/src/mesh/DiamondDualConnectivityBuilder.cpp
+++ b/src/mesh/DiamondDualConnectivityBuilder.cpp
@@ -248,7 +248,7 @@ DiamondDualConnectivityBuilder::_buildDiamondConnectivityFrom(const IConnectivit
           node_array[i] = diamond_node_list[i];
         }
         diamond_descriptor.addRefItemList(
-          RefNodeList{primal_ref_node_list.refId(), node_array, primal_ref_node_list.isBoundary()});
+          RefNodeList{primal_ref_node_list.refId(), node_array, primal_ref_node_list.type()});
       }
     }
   }
@@ -313,7 +313,7 @@ DiamondDualConnectivityBuilder::_buildDiamondConnectivityFrom(const IConnectivit
           face_array[i] = diamond_face_list[i];
         }
         diamond_descriptor.addRefItemList(
-          RefFaceList{primal_ref_face_list.refId(), face_array, primal_ref_face_list.isBoundary()});
+          RefFaceList{primal_ref_face_list.refId(), face_array, primal_ref_face_list.type()});
       }
     }
   }
@@ -383,7 +383,7 @@ DiamondDualConnectivityBuilder::_buildDiamondConnectivityFrom(const IConnectivit
           edge_array[i] = diamond_edge_list[i];
         }
         diamond_descriptor.addRefItemList(
-          RefEdgeList{primal_ref_edge_list.refId(), edge_array, primal_ref_edge_list.isBoundary()});
+          RefEdgeList{primal_ref_edge_list.refId(), edge_array, primal_ref_edge_list.type()});
       }
     }
   }
diff --git a/src/mesh/Dual1DConnectivityBuilder.cpp b/src/mesh/Dual1DConnectivityBuilder.cpp
index 52f3ddc977adc531496bf02b10e2b3ef36dfa5f1..9dd1c743edc0a07f578a4d7fd646ab96cfff490d 100644
--- a/src/mesh/Dual1DConnectivityBuilder.cpp
+++ b/src/mesh/Dual1DConnectivityBuilder.cpp
@@ -145,7 +145,7 @@ Dual1DConnectivityBuilder::_buildConnectivityFrom(const IConnectivity& i_primal_
           node_array[i] = dual_node_list[i];
         }
         dual_descriptor.addRefItemList(
-          RefNodeList{primal_ref_node_list.refId(), node_array, primal_ref_node_list.isBoundary()});
+          RefNodeList{primal_ref_node_list.refId(), node_array, primal_ref_node_list.type()});
       }
     }
   }
diff --git a/src/mesh/GmshReader.cpp b/src/mesh/GmshReader.cpp
index e4ae59ea7cbb6fa2ec3f622c0868ff79ff9f8bbf..73de0466d156f53a114494a1419b9ca506bfea26 100644
--- a/src/mesh/GmshReader.cpp
+++ b/src/mesh/GmshReader.cpp
@@ -83,13 +83,31 @@ GmshConnectivityBuilder<1>::GmshConnectivityBuilder(const GmshReader::GmshData&
       point_list[j] = ref_point_list.second[j];
     }
 
-    bool is_boundary = true;
-    for (size_t i_node = 0; i_node < point_list.size(); ++i_node) {
-      if (node_nb_cell[point_list[i_node]] > 1) {
-        is_boundary = false;
-        break;
+    RefItemListBase::Type list_type = [&] {
+      bool is_boundary = true;
+      for (size_t i_node = 0; i_node < point_list.size(); ++i_node) {
+        if (node_nb_cell[point_list[i_node]] > 1) {
+          is_boundary = false;
+          break;
+        }
+      }
+      if (is_boundary) {
+        return RefItemListBase::Type::boundary;
       }
-    }
+
+      bool is_interface = true;
+      for (size_t i_node = 0; i_node < point_list.size(); ++i_node) {
+        if (node_nb_cell[point_list[i_node]] < 2) {
+          is_interface = false;
+          break;
+        }
+      }
+      if (is_interface) {
+        return RefItemListBase::Type::interface;
+      }
+
+      return RefItemListBase::Type::set;
+    }();
 
     auto ref_id = [&] {
       if (auto i_physical_ref = gmsh_data.m_physical_ref_map.find(ref_point_list.first);
@@ -100,7 +118,7 @@ GmshConnectivityBuilder<1>::GmshConnectivityBuilder(const GmshReader::GmshData&
       }
     }();
 
-    descriptor.addRefItemList(RefNodeList(ref_id, point_list, is_boundary));
+    descriptor.addRefItemList(RefNodeList(ref_id, point_list, list_type));
   }
 
   std::map<unsigned int, std::vector<unsigned int>> ref_cells_map;
@@ -125,7 +143,7 @@ GmshConnectivityBuilder<1>::GmshConnectivityBuilder(const GmshReader::GmshData&
       }
     }();
 
-    descriptor.addRefItemList(RefCellList(ref_id, cell_list, false));
+    descriptor.addRefItemList(RefCellList(ref_id, cell_list, RefItemListBase::Type::set));
   }
 
   descriptor.setCellOwnerVector([&] {
@@ -226,7 +244,7 @@ GmshConnectivityBuilder<2>::GmshConnectivityBuilder(const GmshReader::GmshData&
       }
     }();
 
-    descriptor.addRefItemList(RefCellList(ref_id, cell_list, false));
+    descriptor.addRefItemList(RefCellList(ref_id, cell_list, RefItemListBase::Type::set));
   }
 
   ConnectivityBuilderBase::_computeCellFaceAndFaceNodeConnectivities<2>(descriptor);
@@ -316,15 +334,33 @@ GmshConnectivityBuilder<2>::GmshConnectivityBuilder(const GmshReader::GmshData&
       }
     }();
 
-    bool is_boundary = true;
-    for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
-      if (face_nb_cell[face_list[i_face]] > 1) {
-        is_boundary = false;
-        break;
+    RefItemListBase::Type list_type = [&] {
+      bool is_boundary = true;
+      for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+        if (face_nb_cell[face_list[i_face]] > 1) {
+          is_boundary = false;
+          break;
+        }
       }
-    }
+      if (is_boundary) {
+        return RefItemListBase::Type::boundary;
+      }
+
+      bool is_interface = true;
+      for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+        if (face_nb_cell[face_list[i_face]] < 2) {
+          is_interface = false;
+          break;
+        }
+      }
+      if (is_interface) {
+        return RefItemListBase::Type::interface;
+      }
+
+      return RefItemListBase::Type::set;
+    }();
 
-    descriptor.addRefItemList(RefFaceList{ref_id, face_list, is_boundary});
+    descriptor.addRefItemList(RefFaceList{ref_id, face_list, list_type});
   }
 
   Array<bool> is_boundary_node(node_number_vector.size());
@@ -361,14 +397,22 @@ GmshConnectivityBuilder<2>::GmshConnectivityBuilder(const GmshReader::GmshData&
       }
     }();
 
-    bool is_boundary = true;
-    for (size_t i_node = 0; i_node < point_list.size(); ++i_node) {
-      if (not is_boundary_node[point_list[i_node]]) {
-        is_boundary = false;
+    RefItemListBase::Type list_type = [&] {
+      bool is_boundary = true;
+      for (size_t i_node = 0; i_node < point_list.size(); ++i_node) {
+        if (not is_boundary_node[point_list[i_node]]) {
+          is_boundary = false;
+          break;
+        }
+      }
+      if (is_boundary) {
+        return RefItemListBase::Type::boundary;
       }
-    }
 
-    descriptor.addRefItemList(RefNodeList(ref_id, point_list, is_boundary));
+      return RefItemListBase::Type::set;
+    }();
+
+    descriptor.addRefItemList(RefNodeList(ref_id, point_list, list_type));
   }
 
   descriptor.setCellOwnerVector([&] {
@@ -530,7 +574,7 @@ GmshConnectivityBuilder<3>::GmshConnectivityBuilder(const GmshReader::GmshData&
       }
     }();
 
-    descriptor.addRefItemList(RefCellList(ref_id, cell_list, false));
+    descriptor.addRefItemList(RefCellList(ref_id, cell_list, RefItemListBase::Type::set));
   }
 
   ConnectivityBuilderBase::_computeCellFaceAndFaceNodeConnectivities<3>(descriptor);
@@ -685,14 +729,33 @@ GmshConnectivityBuilder<3>::GmshConnectivityBuilder(const GmshReader::GmshData&
         }
       }();
 
-      bool is_boundary = true;
-      for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
-        if (face_nb_cell[face_list[i_face]] > 1) {
-          is_boundary = false;
-          break;
+      RefItemListBase::Type list_type = [&] {
+        bool is_boundary = true;
+        for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+          if (face_nb_cell[face_list[i_face]] > 1) {
+            is_boundary = false;
+            break;
+          }
         }
-      }
-      descriptor.addRefItemList(RefFaceList(ref_id, face_list, is_boundary));
+        if (is_boundary) {
+          return RefItemListBase::Type::boundary;
+        }
+
+        bool is_interface = true;
+        for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+          if (face_nb_cell[face_list[i_face]] < 2) {
+            is_interface = false;
+            break;
+          }
+        }
+        if (is_interface) {
+          return RefItemListBase::Type::interface;
+        }
+
+        return RefItemListBase::Type::set;
+      }();
+
+      descriptor.addRefItemList(RefFaceList(ref_id, face_list, list_type));
     }
   }
 
@@ -785,14 +848,21 @@ GmshConnectivityBuilder<3>::GmshConnectivityBuilder(const GmshReader::GmshData&
         }
       }();
 
-      bool is_boundary = true;
-      for (size_t i_node = 0; i_node < edge_list.size(); ++i_node) {
-        if (not is_boundary_edge[edge_list[i_node]]) {
-          is_boundary = false;
+      RefItemListBase::Type list_type = [&] {
+        bool is_boundary = true;
+        for (size_t i_edge = 0; i_edge < edge_list.size(); ++i_edge) {
+          if (not is_boundary_edge[edge_list[i_edge]]) {
+            is_boundary = false;
+          }
         }
-      }
+        if (is_boundary) {
+          return RefItemListBase::Type::boundary;
+        }
+
+        return RefItemListBase::Type::set;
+      }();
 
-      descriptor.addRefItemList(RefEdgeList(ref_id, edge_list, is_boundary));
+      descriptor.addRefItemList(RefEdgeList(ref_id, edge_list, list_type));
     }
   }
 
@@ -833,14 +903,21 @@ GmshConnectivityBuilder<3>::GmshConnectivityBuilder(const GmshReader::GmshData&
       }
     }();
 
-    bool is_boundary = true;
-    for (size_t i_node = 0; i_node < point_list.size(); ++i_node) {
-      if (not is_boundary_node[point_list[i_node]]) {
-        is_boundary = false;
+    RefItemListBase::Type list_type = [&] {
+      bool is_boundary = true;
+      for (size_t i_node = 0; i_node < point_list.size(); ++i_node) {
+        if (not is_boundary_node[point_list[i_node]]) {
+          is_boundary = false;
+        }
       }
-    }
+      if (is_boundary) {
+        return RefItemListBase::Type::boundary;
+      }
+
+      return RefItemListBase::Type::set;
+    }();
 
-    descriptor.addRefItemList(RefNodeList(ref_id, point_list, is_boundary));
+    descriptor.addRefItemList(RefNodeList(ref_id, point_list, list_type));
   }
 
   descriptor.setCellOwnerVector([&] {
diff --git a/src/mesh/IInterfaceDescriptor.hpp b/src/mesh/IInterfaceDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4ba1a6a0a288fc13d93c8e5a3fec242a1df56325
--- /dev/null
+++ b/src/mesh/IInterfaceDescriptor.hpp
@@ -0,0 +1,44 @@
+#ifndef I_INTERFACE_DESCRIPTOR_HPP
+#define I_INTERFACE_DESCRIPTOR_HPP
+
+#include <mesh/RefId.hpp>
+
+#include <iostream>
+
+class IInterfaceDescriptor
+{
+ public:
+  enum class Type
+  {
+    named,
+    numbered
+  };
+
+ protected:
+  virtual std::ostream& _write(std::ostream& os) const = 0;
+
+ public:
+  friend std::ostream&
+  operator<<(std::ostream& os, const IInterfaceDescriptor& interface_descriptor)
+  {
+    return interface_descriptor._write(os);
+  }
+
+  [[nodiscard]] virtual bool operator==(const RefId& ref_id) const = 0;
+
+  [[nodiscard]] friend bool
+  operator==(const RefId& ref_id, const IInterfaceDescriptor& interface_descriptor)
+  {
+    return interface_descriptor == ref_id;
+  }
+
+  [[nodiscard]] virtual Type type() const = 0;
+
+  IInterfaceDescriptor(const IInterfaceDescriptor&) = delete;
+  IInterfaceDescriptor(IInterfaceDescriptor&&)      = delete;
+  IInterfaceDescriptor()                            = default;
+
+  virtual ~IInterfaceDescriptor() = default;
+};
+
+#endif   // I_INTERFACE_DESCRIPTOR_HPP
diff --git a/src/mesh/LogicalConnectivityBuilder.cpp b/src/mesh/LogicalConnectivityBuilder.cpp
index dbc41d0681e9ceda822a5896af97ace2e3572f03..0fcbe6752f74f34adcec1d3a2a698ccbe253987a 100644
--- a/src/mesh/LogicalConnectivityBuilder.cpp
+++ b/src/mesh/LogicalConnectivityBuilder.cpp
@@ -24,13 +24,13 @@ LogicalConnectivityBuilder::_buildBoundaryNodeList(
   {   // xmin
     Array<NodeId> boundary_nodes(1);
     boundary_nodes[0] = 0;
-    descriptor.addRefItemList(RefNodeList{RefId{0, "XMIN"}, boundary_nodes, true});
+    descriptor.addRefItemList(RefNodeList{RefId{0, "XMIN"}, boundary_nodes, RefItemListBase::Type::boundary});
   }
 
   {   // xmax
     Array<NodeId> boundary_nodes(1);
     boundary_nodes[0] = cell_size[0];
-    descriptor.addRefItemList(RefNodeList{RefId{1, "XMAX"}, boundary_nodes, true});
+    descriptor.addRefItemList(RefNodeList{RefId{1, "XMAX"}, boundary_nodes, RefItemListBase::Type::boundary});
   }
 }
 
@@ -47,25 +47,25 @@ LogicalConnectivityBuilder::_buildBoundaryNodeList(
   {   // xminymin
     Array<NodeId> boundary_nodes(1);
     boundary_nodes[0] = node_number(0, 0);
-    descriptor.addRefItemList(RefNodeList{RefId{10, "XMINYMIN"}, boundary_nodes, true});
+    descriptor.addRefItemList(RefNodeList{RefId{10, "XMINYMIN"}, boundary_nodes, RefItemListBase::Type::boundary});
   }
 
   {   // xmaxymin
     Array<NodeId> boundary_nodes(1);
     boundary_nodes[0] = node_number(cell_size[0], 0);
-    descriptor.addRefItemList(RefNodeList{RefId{11, "XMAXYMIN"}, boundary_nodes, true});
+    descriptor.addRefItemList(RefNodeList{RefId{11, "XMAXYMIN"}, boundary_nodes, RefItemListBase::Type::boundary});
   }
 
   {   // xmaxymax
     Array<NodeId> boundary_nodes(1);
     boundary_nodes[0] = node_number(cell_size[0], cell_size[1]);
-    descriptor.addRefItemList(RefNodeList{RefId{12, "XMAXYMAX"}, boundary_nodes, true});
+    descriptor.addRefItemList(RefNodeList{RefId{12, "XMAXYMAX"}, boundary_nodes, RefItemListBase::Type::boundary});
   }
 
   {   // xminymax
     Array<NodeId> boundary_nodes(1);
     boundary_nodes[0] = node_number(0, cell_size[1]);
-    descriptor.addRefItemList(RefNodeList{RefId{13, "XMINYMAX"}, boundary_nodes, true});
+    descriptor.addRefItemList(RefNodeList{RefId{13, "XMINYMAX"}, boundary_nodes, RefItemListBase::Type::boundary});
   }
 }
 
@@ -83,49 +83,49 @@ LogicalConnectivityBuilder::_buildBoundaryNodeList(const TinyVector<3, uint64_t>
   {   // xminyminzmin
     Array<NodeId> boundary_nodes(1);
     boundary_nodes[0] = node_number(0, 0, 0);
-    descriptor.addRefItemList(RefNodeList{RefId{10, "XMINYMINZMIN"}, boundary_nodes, true});
+    descriptor.addRefItemList(RefNodeList{RefId{10, "XMINYMINZMIN"}, boundary_nodes, RefItemListBase::Type::boundary});
   }
 
   {   // xmaxyminzmin
     Array<NodeId> boundary_nodes(1);
     boundary_nodes[0] = node_number(cell_size[0], 0, 0);
-    descriptor.addRefItemList(RefNodeList{RefId{11, "XMAXYMINZMIN"}, boundary_nodes, true});
+    descriptor.addRefItemList(RefNodeList{RefId{11, "XMAXYMINZMIN"}, boundary_nodes, RefItemListBase::Type::boundary});
   }
 
   {   // xmaxymaxzmin
     Array<NodeId> boundary_nodes(1);
     boundary_nodes[0] = node_number(cell_size[0], cell_size[1], 0);
-    descriptor.addRefItemList(RefNodeList{RefId{12, "XMAXYMAXZMIN"}, boundary_nodes, true});
+    descriptor.addRefItemList(RefNodeList{RefId{12, "XMAXYMAXZMIN"}, boundary_nodes, RefItemListBase::Type::boundary});
   }
 
   {   // xminymaxzmin
     Array<NodeId> boundary_nodes(1);
     boundary_nodes[0] = node_number(0, cell_size[1], 0);
-    descriptor.addRefItemList(RefNodeList{RefId{13, "XMINYMAXZMIN"}, boundary_nodes, true});
+    descriptor.addRefItemList(RefNodeList{RefId{13, "XMINYMAXZMIN"}, boundary_nodes, RefItemListBase::Type::boundary});
   }
 
   {   // xminyminzmax
     Array<NodeId> boundary_nodes(1);
     boundary_nodes[0] = node_number(0, 0, cell_size[2]);
-    descriptor.addRefItemList(RefNodeList{RefId{14, "XMINYMINZMAX"}, boundary_nodes, true});
+    descriptor.addRefItemList(RefNodeList{RefId{14, "XMINYMINZMAX"}, boundary_nodes, RefItemListBase::Type::boundary});
   }
 
   {   // xmaxyminzmax
     Array<NodeId> boundary_nodes(1);
     boundary_nodes[0] = node_number(cell_size[0], 0, cell_size[2]);
-    descriptor.addRefItemList(RefNodeList{RefId{15, "XMAXYMINZMAX"}, boundary_nodes, true});
+    descriptor.addRefItemList(RefNodeList{RefId{15, "XMAXYMINZMAX"}, boundary_nodes, RefItemListBase::Type::boundary});
   }
 
   {   // xmaxymaxzmax
     Array<NodeId> boundary_nodes(1);
     boundary_nodes[0] = node_number(cell_size[0], cell_size[1], cell_size[2]);
-    descriptor.addRefItemList(RefNodeList{RefId{16, "XMAXYMAXZMAX"}, boundary_nodes, true});
+    descriptor.addRefItemList(RefNodeList{RefId{16, "XMAXYMAXZMAX"}, boundary_nodes, RefItemListBase::Type::boundary});
   }
 
   {   // xminymaxzmax
     Array<NodeId> boundary_nodes(1);
     boundary_nodes[0] = node_number(0, cell_size[1], cell_size[2]);
-    descriptor.addRefItemList(RefNodeList{RefId{17, "XMINYMAXZMAX"}, boundary_nodes, true});
+    descriptor.addRefItemList(RefNodeList{RefId{17, "XMINYMAXZMAX"}, boundary_nodes, RefItemListBase::Type::boundary});
   }
 }
 
@@ -177,7 +177,7 @@ LogicalConnectivityBuilder::_buildBoundaryEdgeList(const TinyVector<3, uint64_t>
         boundary_edges[l++] = find_edge(node_0_id, node_1_id);
       }
       Assert(l == cell_size[2]);
-      descriptor.addRefItemList(RefEdgeList{RefId{ref_id, ref_name}, boundary_edges, true});
+      descriptor.addRefItemList(RefEdgeList{RefId{ref_id, ref_name}, boundary_edges, RefItemListBase::Type::boundary});
     };
 
     add_ref_item_list_along_z(0, 0, 20, "XMINYMIN");
@@ -198,7 +198,7 @@ LogicalConnectivityBuilder::_buildBoundaryEdgeList(const TinyVector<3, uint64_t>
         boundary_edges[l++] = find_edge(node_0_id, node_1_id);
       }
       Assert(l == cell_size[1]);
-      descriptor.addRefItemList(RefEdgeList{RefId{ref_id, ref_name}, boundary_edges, true});
+      descriptor.addRefItemList(RefEdgeList{RefId{ref_id, ref_name}, boundary_edges, RefItemListBase::Type::boundary});
     };
 
     add_ref_item_list_along_y(0, 0, 24, "XMINZMIN");
@@ -219,7 +219,7 @@ LogicalConnectivityBuilder::_buildBoundaryEdgeList(const TinyVector<3, uint64_t>
         boundary_edges[l++] = find_edge(node_0_id, node_1_id);
       }
       Assert(l == cell_size[0]);
-      descriptor.addRefItemList(RefEdgeList{RefId{ref_id, ref_name}, boundary_edges, true});
+      descriptor.addRefItemList(RefEdgeList{RefId{ref_id, ref_name}, boundary_edges, RefItemListBase::Type::boundary});
     };
 
     add_ref_item_list_along_x(0, 0, 28, "YMINZMIN");
@@ -259,7 +259,7 @@ LogicalConnectivityBuilder::_buildBoundaryFaceList(
 
       boundary_faces[j] = face_id;
     }
-    descriptor.addRefItemList(RefFaceList{RefId{0, "XMIN"}, boundary_faces, true});
+    descriptor.addRefItemList(RefFaceList{RefId{0, "XMIN"}, boundary_faces, RefItemListBase::Type::boundary});
   }
 
   {   // xmax
@@ -273,7 +273,7 @@ LogicalConnectivityBuilder::_buildBoundaryFaceList(
 
       boundary_faces[j] = face_id;
     }
-    descriptor.addRefItemList(RefFaceList{RefId{1, "XMAX"}, boundary_faces, true});
+    descriptor.addRefItemList(RefFaceList{RefId{1, "XMAX"}, boundary_faces, RefItemListBase::Type::boundary});
   }
 
   {   // ymin
@@ -287,7 +287,7 @@ LogicalConnectivityBuilder::_buildBoundaryFaceList(
 
       boundary_faces[i] = face_id;
     }
-    descriptor.addRefItemList(RefFaceList{RefId{2, "YMIN"}, boundary_faces, true});
+    descriptor.addRefItemList(RefFaceList{RefId{2, "YMIN"}, boundary_faces, RefItemListBase::Type::boundary});
   }
 
   {   // ymax
@@ -301,7 +301,7 @@ LogicalConnectivityBuilder::_buildBoundaryFaceList(
 
       boundary_faces[i] = face_id;
     }
-    descriptor.addRefItemList(RefFaceList{RefId{3, "YMAX"}, boundary_faces, true});
+    descriptor.addRefItemList(RefFaceList{RefId{3, "YMAX"}, boundary_faces, RefItemListBase::Type::boundary});
   }
 }
 
@@ -375,7 +375,7 @@ LogicalConnectivityBuilder::_buildBoundaryFaceList(const TinyVector<3, uint64_t>
         }
       }
       Assert(l == cell_size[1] * cell_size[2]);
-      descriptor.addRefItemList(RefFaceList{RefId{ref_id, ref_name}, boundary_faces, true});
+      descriptor.addRefItemList(RefFaceList{RefId{ref_id, ref_name}, boundary_faces, RefItemListBase::Type::boundary});
     };
 
     add_ref_item_list_for_x(0, 0, "XMIN");
@@ -397,7 +397,7 @@ LogicalConnectivityBuilder::_buildBoundaryFaceList(const TinyVector<3, uint64_t>
         }
       }
       Assert(l == cell_size[0] * cell_size[2]);
-      descriptor.addRefItemList(RefFaceList{RefId{ref_id, ref_name}, boundary_faces, true});
+      descriptor.addRefItemList(RefFaceList{RefId{ref_id, ref_name}, boundary_faces, RefItemListBase::Type::boundary});
     };
 
     add_ref_item_list_for_y(0, 2, "YMIN");
@@ -419,7 +419,7 @@ LogicalConnectivityBuilder::_buildBoundaryFaceList(const TinyVector<3, uint64_t>
         }
       }
       Assert(l == cell_size[0] * cell_size[1]);
-      descriptor.addRefItemList(RefFaceList{RefId{ref_id, ref_name}, boundary_faces, true});
+      descriptor.addRefItemList(RefFaceList{RefId{ref_id, ref_name}, boundary_faces, RefItemListBase::Type::boundary});
     };
 
     add_ref_item_list_for_z(0, 4, "ZMIN");
diff --git a/src/mesh/MedianDualConnectivityBuilder.cpp b/src/mesh/MedianDualConnectivityBuilder.cpp
index 1986c4a826a0a3ba2754e6204dafb4f9151bd5f7..ef4b260e4ce695a9386cac4f62396a129738a242 100644
--- a/src/mesh/MedianDualConnectivityBuilder.cpp
+++ b/src/mesh/MedianDualConnectivityBuilder.cpp
@@ -319,7 +319,7 @@ MedianDualConnectivityBuilder::_buildConnectivityFrom<2>(const IConnectivity& i_
       if (parallel::allReduceOr(dual_node_list.size() > 0)) {
         auto dual_node_array = convert_to_array(dual_node_list);
         dual_descriptor.addRefItemList(
-          RefNodeList{primal_ref_node_list.refId(), dual_node_array, primal_ref_node_list.isBoundary()});
+          RefNodeList{primal_ref_node_list.refId(), dual_node_array, primal_ref_node_list.type()});
       }
     }
   }
@@ -364,7 +364,7 @@ MedianDualConnectivityBuilder::_buildConnectivityFrom<2>(const IConnectivity& i_
     if (parallel::allReduceOr(boundary_dual_face_id_list.size() > 0)) {
       dual_descriptor.addRefItemList(RefFaceList{primal_ref_face_list.refId(),
                                                  convert_to_array(boundary_dual_face_id_list),
-                                                 primal_ref_face_list.isBoundary()});
+                                                 primal_ref_face_list.type()});
     }
   }
 
diff --git a/src/mesh/MeshEdgeBoundary.cpp b/src/mesh/MeshEdgeBoundary.cpp
index cb522e5b01d3b2498459ec4bdd187693f8cf809f..4c60b3424e93470e0135ee34fb81e870058e2fa9 100644
--- a/src/mesh/MeshEdgeBoundary.cpp
+++ b/src/mesh/MeshEdgeBoundary.cpp
@@ -42,12 +42,12 @@ MeshEdgeBoundary<Dimension>::MeshEdgeBoundary(const Mesh<Connectivity<Dimension>
     Array<EdgeId> edge_list(edge_ids.size());
     parallel_for(
       edge_ids.size(), PUGS_LAMBDA(int r) { edge_list[r] = edge_ids[r]; });
-    m_ref_edge_list = RefEdgeList{ref_face_list.refId(), edge_list, ref_face_list.isBoundary()};
+    m_ref_edge_list = RefEdgeList{ref_face_list.refId(), edge_list, ref_face_list.type()};
   } else if constexpr (Dimension == 2) {
     Array<EdgeId> edge_list(face_list.size());
     parallel_for(
       face_list.size(), PUGS_LAMBDA(int r) { edge_list[r] = static_cast<FaceId::base_type>(face_list[r]); });
-    m_ref_edge_list = RefEdgeList{ref_face_list.refId(), edge_list, ref_face_list.isBoundary()};
+    m_ref_edge_list = RefEdgeList{ref_face_list.refId(), edge_list, ref_face_list.type()};
   }
 
   // This is quite dirty but it allows a non negligible performance
@@ -69,7 +69,7 @@ getMeshEdgeBoundary(const Mesh<Connectivity<Dimension>>& mesh, const IBoundaryDe
 
     if (ref == boundary_descriptor) {
       auto edge_list = ref_edge_list.list();
-      if (not ref_edge_list.isBoundary()) {
+      if (ref_edge_list.type() != RefItemListBase::Type::boundary) {
         std::ostringstream ost;
         ost << "invalid boundary " << rang::fgB::yellow << boundary_descriptor << rang::style::reset
             << ": inner edges cannot be used to define mesh boundaries";
@@ -87,7 +87,7 @@ getMeshEdgeBoundary(const Mesh<Connectivity<Dimension>>& mesh, const IBoundaryDe
 
       if (ref == boundary_descriptor) {
         auto face_list = ref_face_list.list();
-        if (not ref_face_list.isBoundary()) {
+        if (ref_face_list.type() != RefItemListBase::Type::boundary) {
           std::ostringstream ost;
           ost << "invalid boundary " << rang::fgB::yellow << boundary_descriptor << rang::style::reset
               << ": inner edges cannot be used to define mesh boundaries";
diff --git a/src/mesh/MeshEdgeBoundary.hpp b/src/mesh/MeshEdgeBoundary.hpp
index 2f2cd9a5222dd2e09e063965c2949c392bde12b0..e37c4fdffefdc494c97bd8ddbea61f656ef938b7 100644
--- a/src/mesh/MeshEdgeBoundary.hpp
+++ b/src/mesh/MeshEdgeBoundary.hpp
@@ -18,9 +18,6 @@ class [[nodiscard]] MeshEdgeBoundary   // clazy:exclude=copyable-polymorphic
  protected:
   RefEdgeList m_ref_edge_list;
 
-  std::array<TinyVector<Dimension>, Dimension*(Dimension - 1)> _getBounds(const Mesh<Connectivity<Dimension>>& mesh)
-    const;
-
  public:
   template <size_t MeshDimension>
   friend MeshEdgeBoundary<MeshDimension> getMeshEdgeBoundary(const Mesh<Connectivity<MeshDimension>>& mesh,
diff --git a/src/mesh/MeshEdgeInterface.cpp b/src/mesh/MeshEdgeInterface.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..da4d0bc9f1ec42fd68150fab5944d3d446e42dcb
--- /dev/null
+++ b/src/mesh/MeshEdgeInterface.cpp
@@ -0,0 +1,111 @@
+#include <mesh/MeshEdgeInterface.hpp>
+
+#include <Kokkos_Vector.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/Mesh.hpp>
+#include <utils/Messenger.hpp>
+
+template <size_t Dimension>
+MeshEdgeInterface<Dimension>::MeshEdgeInterface(const Mesh<Connectivity<Dimension>>&, const RefEdgeList& ref_edge_list)
+  : m_ref_edge_list(ref_edge_list)
+{}
+
+template MeshEdgeInterface<1>::MeshEdgeInterface(const Mesh<Connectivity<1>>&, const RefEdgeList&);
+template MeshEdgeInterface<2>::MeshEdgeInterface(const Mesh<Connectivity<2>>&, const RefEdgeList&);
+template MeshEdgeInterface<3>::MeshEdgeInterface(const Mesh<Connectivity<3>>&, const RefEdgeList&);
+
+template <size_t Dimension>
+MeshEdgeInterface<Dimension>::MeshEdgeInterface(const Mesh<Connectivity<Dimension>>& mesh,
+                                                const RefFaceList& ref_face_list)
+{
+  const Array<const FaceId>& face_list = ref_face_list.list();
+  static_assert(Dimension > 1, "conversion from to edge from face is valid in dimension > 1");
+
+  if constexpr (Dimension > 2) {
+    Kokkos::vector<unsigned int> edge_ids;
+    // not enough but should reduce significantly the number of resizing
+    edge_ids.reserve(Dimension * face_list.size());
+    const auto& face_to_edge_matrix = mesh.connectivity().faceToEdgeMatrix();
+
+    for (size_t l = 0; l < face_list.size(); ++l) {
+      const FaceId face_number = face_list[l];
+      const auto& face_edges   = face_to_edge_matrix[face_number];
+
+      for (size_t e = 0; e < face_edges.size(); ++e) {
+        edge_ids.push_back(face_edges[e]);
+      }
+    }
+    std::sort(edge_ids.begin(), edge_ids.end());
+    auto last = std::unique(edge_ids.begin(), edge_ids.end());
+    edge_ids.resize(std::distance(edge_ids.begin(), last));
+
+    Array<EdgeId> edge_list(edge_ids.size());
+    parallel_for(
+      edge_ids.size(), PUGS_LAMBDA(int r) { edge_list[r] = edge_ids[r]; });
+    m_ref_edge_list = RefEdgeList{ref_face_list.refId(), edge_list, ref_face_list.type()};
+  } else if constexpr (Dimension == 2) {
+    Array<EdgeId> edge_list(face_list.size());
+    parallel_for(
+      face_list.size(), PUGS_LAMBDA(int r) { edge_list[r] = static_cast<FaceId::base_type>(face_list[r]); });
+    m_ref_edge_list = RefEdgeList{ref_face_list.refId(), edge_list, ref_face_list.type()};
+  }
+
+  // This is quite dirty but it allows a non negligible performance
+  // improvement
+  const_cast<Connectivity<Dimension>&>(mesh.connectivity()).addRefItemList(m_ref_edge_list);
+}
+
+template MeshEdgeInterface<2>::MeshEdgeInterface(const Mesh<Connectivity<2>>&, const RefFaceList&);
+template MeshEdgeInterface<3>::MeshEdgeInterface(const Mesh<Connectivity<3>>&, const RefFaceList&);
+
+template <size_t Dimension>
+MeshEdgeInterface<Dimension>
+getMeshEdgeInterface(const Mesh<Connectivity<Dimension>>& mesh, const IInterfaceDescriptor& interface_descriptor)
+{
+  for (size_t i_ref_edge_list = 0; i_ref_edge_list < mesh.connectivity().template numberOfRefItemList<ItemType::edge>();
+       ++i_ref_edge_list) {
+    const auto& ref_edge_list = mesh.connectivity().template refItemList<ItemType::edge>(i_ref_edge_list);
+    const RefId& ref          = ref_edge_list.refId();
+
+    if (ref == interface_descriptor) {
+      auto edge_list = ref_edge_list.list();
+      if (ref_edge_list.type() != RefItemListBase::Type::interface) {
+        std::ostringstream ost;
+        ost << "invalid interface " << rang::fgB::yellow << interface_descriptor << rang::style::reset
+            << ": boundary edges cannot be used to define mesh interfaces";
+        throw NormalError(ost.str());
+      }
+
+      return MeshEdgeInterface<Dimension>{mesh, ref_edge_list};
+    }
+  }
+  if constexpr (Dimension > 1) {
+    for (size_t i_ref_face_list = 0;
+         i_ref_face_list < mesh.connectivity().template numberOfRefItemList<ItemType::face>(); ++i_ref_face_list) {
+      const auto& ref_face_list = mesh.connectivity().template refItemList<ItemType::face>(i_ref_face_list);
+      const RefId& ref          = ref_face_list.refId();
+
+      if (ref == interface_descriptor) {
+        auto face_list = ref_face_list.list();
+
+        if (ref_face_list.type() != RefItemListBase::Type::interface) {
+          std::ostringstream ost;
+          ost << "invalid interface " << rang::fgB::yellow << interface_descriptor << rang::style::reset
+              << ": boundary faces cannot be used to define mesh interfaces";
+          throw NormalError(ost.str());
+        }
+
+        return MeshEdgeInterface<Dimension>{mesh, ref_face_list};
+      }
+    }
+  }
+
+  std::ostringstream ost;
+  ost << "cannot find edge list with name " << rang::fgB::red << interface_descriptor << rang::style::reset;
+
+  throw NormalError(ost.str());
+}
+
+template MeshEdgeInterface<1> getMeshEdgeInterface(const Mesh<Connectivity<1>>&, const IInterfaceDescriptor&);
+template MeshEdgeInterface<2> getMeshEdgeInterface(const Mesh<Connectivity<2>>&, const IInterfaceDescriptor&);
+template MeshEdgeInterface<3> getMeshEdgeInterface(const Mesh<Connectivity<3>>&, const IInterfaceDescriptor&);
diff --git a/src/mesh/MeshEdgeInterface.hpp b/src/mesh/MeshEdgeInterface.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..25226264c2b906066d2940e6a92a8a65b53042cc
--- /dev/null
+++ b/src/mesh/MeshEdgeInterface.hpp
@@ -0,0 +1,57 @@
+#ifndef MESH_EDGE_INTERFACE_HPP
+#define MESH_EDGE_INTERFACE_HPP
+
+#include <algebra/TinyVector.hpp>
+#include <mesh/IInterfaceDescriptor.hpp>
+#include <mesh/RefItemList.hpp>
+#include <utils/Array.hpp>
+
+template <size_t Dimension>
+class Connectivity;
+
+template <typename ConnectivityType>
+class Mesh;
+
+template <size_t Dimension>
+class [[nodiscard]] MeshEdgeInterface   // clazy:exclude=copyable-polymorphic
+{
+ protected:
+  RefEdgeList m_ref_edge_list;
+
+ public:
+  template <size_t MeshDimension>
+  friend MeshEdgeInterface<MeshDimension> getMeshEdgeInterface(const Mesh<Connectivity<MeshDimension>>& mesh,
+                                                               const IInterfaceDescriptor& interface_descriptor);
+
+  MeshEdgeInterface& operator=(const MeshEdgeInterface&) = default;
+  MeshEdgeInterface& operator=(MeshEdgeInterface&&) = default;
+
+  PUGS_INLINE
+  const RefEdgeList& refEdgeList() const
+  {
+    return m_ref_edge_list;
+  }
+
+  PUGS_INLINE
+  const Array<const EdgeId>& edgeList() const
+  {
+    return m_ref_edge_list.list();
+  }
+
+ protected:
+  MeshEdgeInterface(const Mesh<Connectivity<Dimension>>& mesh, const RefEdgeList& ref_edge_list);
+  MeshEdgeInterface(const Mesh<Connectivity<Dimension>>& mesh, const RefFaceList& ref_face_list);
+
+ public:
+  MeshEdgeInterface(const MeshEdgeInterface&) = default;   // LCOV_EXCL_LINE
+  MeshEdgeInterface(MeshEdgeInterface &&)     = default;   // LCOV_EXCL_LINE
+
+  MeshEdgeInterface()          = default;
+  virtual ~MeshEdgeInterface() = default;
+};
+
+template <size_t Dimension>
+MeshEdgeInterface<Dimension> getMeshEdgeInterface(const Mesh<Connectivity<Dimension>>& mesh,
+                                                  const IInterfaceDescriptor& interface_descriptor);
+
+#endif   // MESH_EDGE_INTERFACE_HPP
diff --git a/src/mesh/MeshFaceBoundary.cpp b/src/mesh/MeshFaceBoundary.cpp
index ce18bd150ebb0810ae00b15bb7fc59ea919be7bd..ad77830f5cbcb10070cfb7cb4989a7065a650f16 100644
--- a/src/mesh/MeshFaceBoundary.cpp
+++ b/src/mesh/MeshFaceBoundary.cpp
@@ -24,7 +24,7 @@ getMeshFaceBoundary(const Mesh<Connectivity<Dimension>>& mesh, const IBoundaryDe
 
     if (ref == boundary_descriptor) {
       auto face_list = ref_face_list.list();
-      if (not ref_face_list.isBoundary()) {
+      if (ref_face_list.type() != RefItemListBase::Type::boundary) {
         std::ostringstream ost;
         ost << "invalid boundary " << rang::fgB::yellow << boundary_descriptor << rang::style::reset
             << ": inner faces cannot be used to define mesh boundaries";
diff --git a/src/mesh/MeshFaceBoundary.hpp b/src/mesh/MeshFaceBoundary.hpp
index fd52128a3c960b43c5e432416c7e46a900d8eade..98a63774138e60bbb01437ee4fb6d88bd1400e40 100644
--- a/src/mesh/MeshFaceBoundary.hpp
+++ b/src/mesh/MeshFaceBoundary.hpp
@@ -18,9 +18,6 @@ class [[nodiscard]] MeshFaceBoundary   // clazy:exclude=copyable-polymorphic
  protected:
   RefFaceList m_ref_face_list;
 
-  std::array<TinyVector<Dimension>, Dimension*(Dimension - 1)> _getBounds(const Mesh<Connectivity<Dimension>>& mesh)
-    const;
-
  public:
   template <size_t MeshDimension>
   friend MeshFaceBoundary<MeshDimension> getMeshFaceBoundary(const Mesh<Connectivity<MeshDimension>>& mesh,
diff --git a/src/mesh/MeshFaceInterface.cpp b/src/mesh/MeshFaceInterface.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..afb73bf1872ece258788d1a4c6818f9d588250b6
--- /dev/null
+++ b/src/mesh/MeshFaceInterface.cpp
@@ -0,0 +1,45 @@
+#include <mesh/MeshFaceInterface.hpp>
+
+#include <mesh/Connectivity.hpp>
+#include <mesh/Mesh.hpp>
+#include <utils/Messenger.hpp>
+
+template <size_t Dimension>
+MeshFaceInterface<Dimension>::MeshFaceInterface(const Mesh<Connectivity<Dimension>>&, const RefFaceList& ref_face_list)
+  : m_ref_face_list(ref_face_list)
+{}
+
+template MeshFaceInterface<1>::MeshFaceInterface(const Mesh<Connectivity<1>>&, const RefFaceList&);
+template MeshFaceInterface<2>::MeshFaceInterface(const Mesh<Connectivity<2>>&, const RefFaceList&);
+template MeshFaceInterface<3>::MeshFaceInterface(const Mesh<Connectivity<3>>&, const RefFaceList&);
+
+template <size_t Dimension>
+MeshFaceInterface<Dimension>
+getMeshFaceInterface(const Mesh<Connectivity<Dimension>>& mesh, const IInterfaceDescriptor& interface_descriptor)
+{
+  for (size_t i_ref_face_list = 0; i_ref_face_list < mesh.connectivity().template numberOfRefItemList<ItemType::face>();
+       ++i_ref_face_list) {
+    const auto& ref_face_list = mesh.connectivity().template refItemList<ItemType::face>(i_ref_face_list);
+    const RefId& ref          = ref_face_list.refId();
+
+    if (ref == interface_descriptor) {
+      if (ref_face_list.type() != RefItemListBase::Type::interface) {
+        std::ostringstream ost;
+        ost << "invalid interface " << rang::fgB::yellow << interface_descriptor << rang::style::reset
+            << ": boundary faces cannot be used to define mesh interfaces";
+        throw NormalError(ost.str());
+      }
+
+      return MeshFaceInterface<Dimension>{mesh, ref_face_list};
+    }
+  }
+
+  std::ostringstream ost;
+  ost << "cannot find face list with name " << rang::fgB::red << interface_descriptor << rang::style::reset;
+
+  throw NormalError(ost.str());
+}
+
+template MeshFaceInterface<1> getMeshFaceInterface(const Mesh<Connectivity<1>>&, const IInterfaceDescriptor&);
+template MeshFaceInterface<2> getMeshFaceInterface(const Mesh<Connectivity<2>>&, const IInterfaceDescriptor&);
+template MeshFaceInterface<3> getMeshFaceInterface(const Mesh<Connectivity<3>>&, const IInterfaceDescriptor&);
diff --git a/src/mesh/MeshFaceInterface.hpp b/src/mesh/MeshFaceInterface.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..87d836a3705199aab546396d24052025b59b5729
--- /dev/null
+++ b/src/mesh/MeshFaceInterface.hpp
@@ -0,0 +1,56 @@
+#ifndef MESH_FACE_INTERFACE_HPP
+#define MESH_FACE_INTERFACE_HPP
+
+#include <algebra/TinyVector.hpp>
+#include <mesh/IInterfaceDescriptor.hpp>
+#include <mesh/RefItemList.hpp>
+#include <utils/Array.hpp>
+
+template <size_t Dimension>
+class Connectivity;
+
+template <typename ConnectivityType>
+class Mesh;
+
+template <size_t Dimension>
+class [[nodiscard]] MeshFaceInterface   // clazy:exclude=copyable-polymorphic
+{
+ protected:
+  RefFaceList m_ref_face_list;
+
+ public:
+  template <size_t MeshDimension>
+  friend MeshFaceInterface<MeshDimension> getMeshFaceInterface(const Mesh<Connectivity<MeshDimension>>& mesh,
+                                                               const IInterfaceDescriptor& interface_descriptor);
+
+  MeshFaceInterface& operator=(const MeshFaceInterface&) = default;
+  MeshFaceInterface& operator=(MeshFaceInterface&&) = default;
+
+  PUGS_INLINE
+  const RefFaceList& refFaceList() const
+  {
+    return m_ref_face_list;
+  }
+
+  PUGS_INLINE
+  const Array<const FaceId>& faceList() const
+  {
+    return m_ref_face_list.list();
+  }
+
+ protected:
+  MeshFaceInterface(const Mesh<Connectivity<Dimension>>& mesh, const RefFaceList& ref_face_list);
+
+ public:
+  MeshFaceInterface(const MeshFaceInterface&) = default;   // LCOV_EXCL_LINE
+  MeshFaceInterface(MeshFaceInterface &&)     = default;   // LCOV_EXCL_LINE
+
+  MeshFaceInterface()          = default;
+  virtual ~MeshFaceInterface() = default;
+};
+
+template <size_t Dimension>
+MeshFaceInterface<Dimension> getMeshFaceInterface(const Mesh<Connectivity<Dimension>>& mesh,
+                                                  const IInterfaceDescriptor& interface_descriptor);
+
+#endif   // MESH_FACE_INTERFACE_HPP
diff --git a/src/mesh/MeshNodeBoundary.cpp b/src/mesh/MeshNodeBoundary.cpp
index 3e01616c66670706449018107aa3d417ab1d5007..d97ee08bff9e38a7945490a2441b8e6348c84150 100644
--- a/src/mesh/MeshNodeBoundary.cpp
+++ b/src/mesh/MeshNodeBoundary.cpp
@@ -175,7 +175,7 @@ MeshNodeBoundary<Dimension>::MeshNodeBoundary(const Mesh<Connectivity<Dimension>
                                               const RefFaceList& ref_face_list)
 {
   const Array<const FaceId>& face_list = ref_face_list.list();
-  if (not ref_face_list.isBoundary()) {
+  if (ref_face_list.type() != RefItemListBase::Type::boundary) {
     std::ostringstream ost;
     ost << "invalid boundary \"" << rang::fgB::yellow << ref_face_list.refId() << rang::style::reset
         << "\": inner faces cannot be used to define mesh boundaries";
@@ -203,12 +203,12 @@ MeshNodeBoundary<Dimension>::MeshNodeBoundary(const Mesh<Connectivity<Dimension>
     Array<NodeId> node_list(node_ids.size());
     parallel_for(
       node_ids.size(), PUGS_LAMBDA(int r) { node_list[r] = node_ids[r]; });
-    m_ref_node_list = RefNodeList{ref_face_list.refId(), node_list, ref_face_list.isBoundary()};
+    m_ref_node_list = RefNodeList{ref_face_list.refId(), node_list, ref_face_list.type()};
   } else {
     Array<NodeId> node_list(face_list.size());
     parallel_for(
       face_list.size(), PUGS_LAMBDA(int r) { node_list[r] = static_cast<FaceId::base_type>(face_list[r]); });
-    m_ref_node_list = RefNodeList{ref_face_list.refId(), node_list, ref_face_list.isBoundary()};
+    m_ref_node_list = RefNodeList{ref_face_list.refId(), node_list, ref_face_list.type()};
   }
 
   // This is quite dirty but it allows a non negligible performance
@@ -221,7 +221,7 @@ MeshNodeBoundary<Dimension>::MeshNodeBoundary(const Mesh<Connectivity<Dimension>
                                               const RefEdgeList& ref_edge_list)
 {
   const Array<const EdgeId>& edge_list = ref_edge_list.list();
-  if (not ref_edge_list.isBoundary()) {
+  if (ref_edge_list.type() != RefItemListBase::Type::boundary) {
     std::ostringstream ost;
     ost << "invalid boundary \"" << rang::fgB::yellow << ref_edge_list.refId() << rang::style::reset
         << "\": inner edges cannot be used to define mesh boundaries";
@@ -248,12 +248,12 @@ MeshNodeBoundary<Dimension>::MeshNodeBoundary(const Mesh<Connectivity<Dimension>
     Array<NodeId> node_list(node_ids.size());
     parallel_for(
       node_ids.size(), PUGS_LAMBDA(int r) { node_list[r] = node_ids[r]; });
-    m_ref_node_list = RefNodeList{ref_edge_list.refId(), node_list, ref_edge_list.isBoundary()};
+    m_ref_node_list = RefNodeList{ref_edge_list.refId(), node_list, ref_edge_list.type()};
   } else {
     Array<NodeId> node_list(edge_list.size());
     parallel_for(
       edge_list.size(), PUGS_LAMBDA(int r) { node_list[r] = static_cast<EdgeId::base_type>(edge_list[r]); });
-    m_ref_node_list = RefNodeList{ref_edge_list.refId(), node_list, ref_edge_list.isBoundary()};
+    m_ref_node_list = RefNodeList{ref_edge_list.refId(), node_list, ref_edge_list.type()};
   }
 
   // This is quite dirty but it allows a non negligible performance
@@ -265,7 +265,7 @@ template <size_t Dimension>
 MeshNodeBoundary<Dimension>::MeshNodeBoundary(const Mesh<Connectivity<Dimension>>&, const RefNodeList& ref_node_list)
   : m_ref_node_list(ref_node_list)
 {
-  if (not ref_node_list.isBoundary()) {
+  if (ref_node_list.type() != RefItemListBase::Type::boundary) {
     std::ostringstream ost;
     ost << "invalid boundary \"" << rang::fgB::yellow << this->m_ref_node_list.refId() << rang::style::reset
         << "\": inner nodes cannot be used to define mesh boundaries";
diff --git a/src/mesh/MeshNodeInterface.cpp b/src/mesh/MeshNodeInterface.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..84a5b2669a27bedbd8e11e8ffbe67d1860051f56
--- /dev/null
+++ b/src/mesh/MeshNodeInterface.cpp
@@ -0,0 +1,160 @@
+#include <mesh/MeshNodeInterface.hpp>
+
+#include <Kokkos_Vector.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/Mesh.hpp>
+#include <utils/Messenger.hpp>
+
+template <size_t Dimension>
+MeshNodeInterface<Dimension>::MeshNodeInterface(const Mesh<Connectivity<Dimension>>& mesh,
+                                                const RefFaceList& ref_face_list)
+{
+  const Array<const FaceId>& face_list = ref_face_list.list();
+  if (ref_face_list.type() != RefItemListBase::Type::interface) {
+    std::ostringstream ost;
+    ost << "invalid interface \"" << rang::fgB::yellow << ref_face_list.refId() << rang::style::reset
+        << "\": boundary faces cannot be used to define mesh interfaces";
+    throw NormalError(ost.str());
+  }
+
+  if constexpr (Dimension > 1) {
+    Kokkos::vector<unsigned int> node_ids;
+    // not enough but should reduce significantly the number of resizing
+    node_ids.reserve(Dimension * face_list.size());
+    const auto& face_to_node_matrix = mesh.connectivity().faceToNodeMatrix();
+
+    for (size_t l = 0; l < face_list.size(); ++l) {
+      const FaceId face_number = face_list[l];
+      const auto& face_nodes   = face_to_node_matrix[face_number];
+
+      for (size_t r = 0; r < face_nodes.size(); ++r) {
+        node_ids.push_back(face_nodes[r]);
+      }
+    }
+    std::sort(node_ids.begin(), node_ids.end());
+    auto last = std::unique(node_ids.begin(), node_ids.end());
+    node_ids.resize(std::distance(node_ids.begin(), last));
+
+    Array<NodeId> node_list(node_ids.size());
+    parallel_for(
+      node_ids.size(), PUGS_LAMBDA(int r) { node_list[r] = node_ids[r]; });
+    m_ref_node_list = RefNodeList{ref_face_list.refId(), node_list, ref_face_list.type()};
+  } else {
+    Array<NodeId> node_list(face_list.size());
+    parallel_for(
+      face_list.size(), PUGS_LAMBDA(int r) { node_list[r] = static_cast<FaceId::base_type>(face_list[r]); });
+    m_ref_node_list = RefNodeList{ref_face_list.refId(), node_list, ref_face_list.type()};
+  }
+
+  // This is quite dirty but it allows a non negligible performance
+  // improvement
+  const_cast<Connectivity<Dimension>&>(mesh.connectivity()).addRefItemList(m_ref_node_list);
+}
+
+template <size_t Dimension>
+MeshNodeInterface<Dimension>::MeshNodeInterface(const Mesh<Connectivity<Dimension>>& mesh,
+                                                const RefEdgeList& ref_edge_list)
+{
+  const Array<const EdgeId>& edge_list = ref_edge_list.list();
+  if (ref_edge_list.type() != RefItemListBase::Type::interface) {
+    std::ostringstream ost;
+    ost << "invalid interface \"" << rang::fgB::yellow << ref_edge_list.refId() << rang::style::reset
+        << "\": boundary edges cannot be used to define mesh interfaces";
+    throw NormalError(ost.str());
+  }
+
+  if constexpr (Dimension > 1) {
+    const auto& edge_to_node_matrix = mesh.connectivity().edgeToNodeMatrix();
+    Kokkos::vector<unsigned int> node_ids;
+    node_ids.reserve(2 * edge_list.size());
+
+    for (size_t l = 0; l < edge_list.size(); ++l) {
+      const EdgeId edge_number = edge_list[l];
+      const auto& edge_nodes   = edge_to_node_matrix[edge_number];
+
+      for (size_t r = 0; r < edge_nodes.size(); ++r) {
+        node_ids.push_back(edge_nodes[r]);
+      }
+    }
+    std::sort(node_ids.begin(), node_ids.end());
+    auto last = std::unique(node_ids.begin(), node_ids.end());
+    node_ids.resize(std::distance(node_ids.begin(), last));
+
+    Array<NodeId> node_list(node_ids.size());
+    parallel_for(
+      node_ids.size(), PUGS_LAMBDA(int r) { node_list[r] = node_ids[r]; });
+    m_ref_node_list = RefNodeList{ref_edge_list.refId(), node_list, ref_edge_list.type()};
+  } else {
+    Array<NodeId> node_list(edge_list.size());
+    parallel_for(
+      edge_list.size(), PUGS_LAMBDA(int r) { node_list[r] = static_cast<EdgeId::base_type>(edge_list[r]); });
+    m_ref_node_list = RefNodeList{ref_edge_list.refId(), node_list, ref_edge_list.type()};
+  }
+
+  // This is quite dirty but it allows a non negligible performance
+  // improvement
+  const_cast<Connectivity<Dimension>&>(mesh.connectivity()).addRefItemList(m_ref_node_list);
+}
+
+template <size_t Dimension>
+MeshNodeInterface<Dimension>::MeshNodeInterface(const Mesh<Connectivity<Dimension>>&, const RefNodeList& ref_node_list)
+  : m_ref_node_list(ref_node_list)
+{
+  if (ref_node_list.type() != RefItemListBase::Type::interface) {
+    std::ostringstream ost;
+    ost << "invalid interface \"" << rang::fgB::yellow << ref_node_list.refId() << rang::style::reset
+        << "\": boundary nodes cannot be used to define mesh interfaces";
+    throw NormalError(ost.str());
+  }
+}
+
+template MeshNodeInterface<1>::MeshNodeInterface(const Mesh<Connectivity<1>>&, const RefFaceList&);
+template MeshNodeInterface<2>::MeshNodeInterface(const Mesh<Connectivity<2>>&, const RefFaceList&);
+template MeshNodeInterface<3>::MeshNodeInterface(const Mesh<Connectivity<3>>&, const RefFaceList&);
+
+template MeshNodeInterface<1>::MeshNodeInterface(const Mesh<Connectivity<1>>&, const RefEdgeList&);
+template MeshNodeInterface<2>::MeshNodeInterface(const Mesh<Connectivity<2>>&, const RefEdgeList&);
+template MeshNodeInterface<3>::MeshNodeInterface(const Mesh<Connectivity<3>>&, const RefEdgeList&);
+
+template MeshNodeInterface<1>::MeshNodeInterface(const Mesh<Connectivity<1>>&, const RefNodeList&);
+template MeshNodeInterface<2>::MeshNodeInterface(const Mesh<Connectivity<2>>&, const RefNodeList&);
+template MeshNodeInterface<3>::MeshNodeInterface(const Mesh<Connectivity<3>>&, const RefNodeList&);
+
+template <size_t Dimension>
+MeshNodeInterface<Dimension>
+getMeshNodeInterface(const Mesh<Connectivity<Dimension>>& mesh, const IInterfaceDescriptor& interface_descriptor)
+{
+  for (size_t i_ref_node_list = 0; i_ref_node_list < mesh.connectivity().template numberOfRefItemList<ItemType::node>();
+       ++i_ref_node_list) {
+    const auto& ref_node_list = mesh.connectivity().template refItemList<ItemType::node>(i_ref_node_list);
+    const RefId& ref          = ref_node_list.refId();
+    if (ref == interface_descriptor) {
+      return MeshNodeInterface<Dimension>{mesh, ref_node_list};
+    }
+  }
+  for (size_t i_ref_edge_list = 0; i_ref_edge_list < mesh.connectivity().template numberOfRefItemList<ItemType::edge>();
+       ++i_ref_edge_list) {
+    const auto& ref_edge_list = mesh.connectivity().template refItemList<ItemType::edge>(i_ref_edge_list);
+    const RefId& ref          = ref_edge_list.refId();
+    if (ref == interface_descriptor) {
+      return MeshNodeInterface<Dimension>{mesh, ref_edge_list};
+    }
+  }
+  for (size_t i_ref_face_list = 0; i_ref_face_list < mesh.connectivity().template numberOfRefItemList<ItemType::face>();
+       ++i_ref_face_list) {
+    const auto& ref_face_list = mesh.connectivity().template refItemList<ItemType::face>(i_ref_face_list);
+    const RefId& ref          = ref_face_list.refId();
+    if (ref == interface_descriptor) {
+      return MeshNodeInterface<Dimension>{mesh, ref_face_list};
+    }
+  }
+
+  std::ostringstream ost;
+  ost << "cannot find node list with name " << rang::fgB::red << interface_descriptor << rang::style::reset;
+
+  throw NormalError(ost.str());
+}
+
+template MeshNodeInterface<1> getMeshNodeInterface(const Mesh<Connectivity<1>>&, const IInterfaceDescriptor&);
+template MeshNodeInterface<2> getMeshNodeInterface(const Mesh<Connectivity<2>>&, const IInterfaceDescriptor&);
+template MeshNodeInterface<3> getMeshNodeInterface(const Mesh<Connectivity<3>>&, const IInterfaceDescriptor&);
diff --git a/src/mesh/MeshNodeInterface.hpp b/src/mesh/MeshNodeInterface.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8fcbb6704df80ffa48697fd20109e4fab36e2b98
--- /dev/null
+++ b/src/mesh/MeshNodeInterface.hpp
@@ -0,0 +1,61 @@
+#ifndef MESH_NODE_INTERFACE_HPP
+#define MESH_NODE_INTERFACE_HPP
+
+#include <algebra/TinyVector.hpp>
+#include <mesh/IInterfaceDescriptor.hpp>
+#include <mesh/RefItemList.hpp>
+#include <utils/Array.hpp>
+
+template <size_t Dimension>
+class Connectivity;
+
+template <typename ConnectivityType>
+class Mesh;
+
+template <size_t Dimension>
+class [[nodiscard]] MeshNodeInterface   // clazy:exclude=copyable-polymorphic
+{
+ protected:
+  RefNodeList m_ref_node_list;
+
+  std::array<TinyVector<Dimension>, Dimension*(Dimension - 1)> _getBounds(const Mesh<Connectivity<Dimension>>& mesh)
+    const;
+
+ public:
+  template <size_t MeshDimension>
+  friend MeshNodeInterface<MeshDimension> getMeshNodeInterface(const Mesh<Connectivity<MeshDimension>>& mesh,
+                                                               const IInterfaceDescriptor& interface_descriptor);
+
+  MeshNodeInterface& operator=(const MeshNodeInterface&) = default;
+  MeshNodeInterface& operator=(MeshNodeInterface&&) = default;
+
+  PUGS_INLINE
+  const RefNodeList& refNodeList() const
+  {
+    return m_ref_node_list;
+  }
+
+  PUGS_INLINE
+  const Array<const NodeId>& nodeList() const
+  {
+    return m_ref_node_list.list();
+  }
+
+ protected:
+  MeshNodeInterface(const Mesh<Connectivity<Dimension>>& mesh, const RefFaceList& ref_face_list);
+  MeshNodeInterface(const Mesh<Connectivity<Dimension>>& mesh, const RefEdgeList& ref_edge_list);
+  MeshNodeInterface(const Mesh<Connectivity<Dimension>>&, const RefNodeList& ref_node_list);
+
+ public:
+  MeshNodeInterface(const MeshNodeInterface&) = default;
+  MeshNodeInterface(MeshNodeInterface &&)     = default;
+
+  MeshNodeInterface()          = default;
+  virtual ~MeshNodeInterface() = default;
+};
+
+template <size_t Dimension>
+MeshNodeInterface<Dimension> getMeshNodeInterface(const Mesh<Connectivity<Dimension>>& mesh,
+                                                  const IInterfaceDescriptor& interface_descriptor);
+
+#endif   // MESH_NODE_INTERFACE_HPP
diff --git a/src/mesh/NamedInterfaceDescriptor.hpp b/src/mesh/NamedInterfaceDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0b6a0e996c4753ee2f33e69f482820520886ad19
--- /dev/null
+++ b/src/mesh/NamedInterfaceDescriptor.hpp
@@ -0,0 +1,40 @@
+#ifndef NAMED_INTERFACE_DESCRIPTOR_HPP
+#define NAMED_INTERFACE_DESCRIPTOR_HPP
+
+#include <mesh/IInterfaceDescriptor.hpp>
+
+#include <iostream>
+#include <string>
+
+class NamedInterfaceDescriptor final : public IInterfaceDescriptor
+{
+ private:
+  std::string m_name;
+
+  std::ostream&
+  _write(std::ostream& os) const final
+  {
+    os << '"' << m_name << '"';
+    return os;
+  }
+
+ public:
+  [[nodiscard]] bool
+  operator==(const RefId& ref_id) const final
+  {
+    return m_name == ref_id.tagName();
+  }
+
+  [[nodiscard]] Type
+  type() const final
+  {
+    return Type::named;
+  }
+
+  NamedInterfaceDescriptor(const NamedInterfaceDescriptor&) = delete;
+  NamedInterfaceDescriptor(NamedInterfaceDescriptor&&)      = delete;
+  NamedInterfaceDescriptor(const std::string& name) : m_name(name) {}
+  virtual ~NamedInterfaceDescriptor() = default;
+};
+
+#endif   // NAMED_INTERFACE_DESCRIPTOR_HPP
diff --git a/src/mesh/NumberedInterfaceDescriptor.hpp b/src/mesh/NumberedInterfaceDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d72197221f5811a22836871e89f601479d480123
--- /dev/null
+++ b/src/mesh/NumberedInterfaceDescriptor.hpp
@@ -0,0 +1,42 @@
+#ifndef NUMBERED_INTERFACE_DESCRIPTOR_HPP
+#define NUMBERED_INTERFACE_DESCRIPTOR_HPP
+
+#include <mesh/IInterfaceDescriptor.hpp>
+
+#include <iostream>
+
+class NumberedInterfaceDescriptor final : public IInterfaceDescriptor
+{
+ private:
+  unsigned int m_number;
+
+  std::ostream&
+  _write(std::ostream& os) const final
+  {
+    os << '"' << m_number << '"';
+    return os;
+  }
+
+  [[nodiscard]] bool
+  operator==(const RefId& ref_id) const final
+  {
+    return m_number == ref_id.tagNumber();
+  }
+
+ public:
+  [[nodiscard]] Type
+  type() const final
+  {
+    return Type::numbered;
+  }
+
+  NumberedInterfaceDescriptor(const NumberedInterfaceDescriptor&) = delete;
+  NumberedInterfaceDescriptor(NumberedInterfaceDescriptor&&)      = delete;
+  NumberedInterfaceDescriptor(unsigned int number) : m_number(number)
+  {
+    ;
+  }
+  virtual ~NumberedInterfaceDescriptor() = default;
+};
+
+#endif   // NUMBERED_INTERFACE_DESCRIPTOR_HPP
diff --git a/src/mesh/RefItemList.hpp b/src/mesh/RefItemList.hpp
index 326e3278c948baae0ad8dd4d562399563a6f993a..182826d20a1608f2b235295143cbb8e653deb136 100644
--- a/src/mesh/RefItemList.hpp
+++ b/src/mesh/RefItemList.hpp
@@ -6,8 +6,42 @@
 #include <mesh/ItemId.hpp>
 #include <mesh/RefId.hpp>
 
+class RefItemListBase
+{
+ public:
+  enum class Type
+  {
+    boundary,
+    interface,
+    set,
+    undefined
+  };
+
+ protected:
+  Type m_type;
+
+  RefItemListBase(Type type) : m_type{type} {}
+
+  RefItemListBase() : m_type{Type::undefined} {}
+
+  RefItemListBase(const RefItemListBase&) = default;
+  RefItemListBase(RefItemListBase&&)      = default;
+
+  RefItemListBase& operator=(const RefItemListBase&) = default;
+  RefItemListBase& operator=(RefItemListBase&&) = default;
+
+ public:
+  PUGS_INLINE Type
+  type() const
+  {
+    return m_type;
+  }
+
+  virtual ~RefItemListBase() = default;
+};
+
 template <ItemType item_type>
-class RefItemList
+class RefItemList final : public RefItemListBase
 {
  public:
   using ItemId = ItemIdT<item_type>;
@@ -15,7 +49,6 @@ class RefItemList
  private:
   RefId m_ref_id;
   Array<const ItemId> m_item_id_list;
-  bool m_is_boundary;
 
  public:
   const RefId&
@@ -30,14 +63,8 @@ class RefItemList
     return m_item_id_list;
   }
 
-  bool
-  isBoundary() const
-  {
-    return m_is_boundary;
-  }
-
-  RefItemList(const RefId& ref_id, const Array<const ItemId>& item_id_list, bool is_boundary)
-    : m_ref_id{ref_id}, m_item_id_list{item_id_list}, m_is_boundary{is_boundary}
+  RefItemList(const RefId& ref_id, const Array<const ItemId>& item_id_list, Type type)
+    : RefItemListBase{type}, m_ref_id{ref_id}, m_item_id_list{item_id_list}
   {
     ;
   }
@@ -47,6 +74,7 @@ class RefItemList
 
   RefItemList()                   = default;
   RefItemList(const RefItemList&) = default;
+  RefItemList(RefItemList&&)      = default;
   ~RefItemList()                  = default;
 };
 
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 9f1f7d9005d6aa8a583283bf2bfda8dc6e23eedd..659e7760166b89a4772395125579781b567847b2 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -185,7 +185,9 @@ add_executable (mpi_unit_tests
   test_ItemValueVariant.cpp
   test_ItemValueVariantFunctionInterpoler.cpp
   test_MeshEdgeBoundary.cpp
+  test_MeshEdgeInterface.cpp
   test_MeshFaceBoundary.cpp
+  test_MeshFaceInterface.cpp
   test_MeshFlatEdgeBoundary.cpp
   test_MeshFlatFaceBoundary.cpp
   test_MeshFlatNodeBoundary.cpp
@@ -193,6 +195,7 @@ add_executable (mpi_unit_tests
   test_MeshLineFaceBoundary.cpp
   test_MeshLineNodeBoundary.cpp
   test_MeshNodeBoundary.cpp
+  test_MeshNodeInterface.cpp
   test_Messenger.cpp
   test_OFStream.cpp
   test_Partitioner.cpp
diff --git a/tests/test_MeshEdgeBoundary.cpp b/tests/test_MeshEdgeBoundary.cpp
index 07711f27dc7aad4c4da9a18dafb2e8574e5e1cc6..74facdecf52bd9dce75e8ebff557415d4cf8f691 100644
--- a/tests/test_MeshEdgeBoundary.cpp
+++ b/tests/test_MeshEdgeBoundary.cpp
@@ -278,7 +278,7 @@ TEST_CASE("MeshEdgeBoundary", "[mesh]")
                           "error: cannot find edge list with name \"invalid_boundary\"");
     }
 
-    SECTION("suredge is inside")
+    SECTION("surface is inside")
     {
       SECTION("1D")
       {
diff --git a/tests/test_MeshEdgeInterface.cpp b/tests/test_MeshEdgeInterface.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a42258e45510177e5192a39712cad9323658ce38
--- /dev/null
+++ b/tests/test_MeshEdgeInterface.cpp
@@ -0,0 +1,184 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+
+#include <mesh/Connectivity.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshEdgeInterface.hpp>
+#include <mesh/NamedInterfaceDescriptor.hpp>
+#include <mesh/NumberedInterfaceDescriptor.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("MeshEdgeInterface", "[mesh]")
+{
+  auto is_same = [](const auto& a, const auto& b) -> bool {
+    if (a.size() > 0 and b.size() > 0) {
+      return (a.size() == b.size()) and (&(a[0]) == &(b[0]));
+    } else {
+      return (a.size() == b.size());
+    }
+  };
+
+  auto get_edge_list_from_tag = [](const size_t tag, const auto& connectivity) -> Array<const EdgeId> {
+    for (size_t i = 0; i < connectivity.template numberOfRefItemList<ItemType::edge>(); ++i) {
+      const auto& ref_edge_list = connectivity.template refItemList<ItemType::edge>(i);
+      const RefId ref_id        = ref_edge_list.refId();
+      if (ref_id.tagNumber() == tag) {
+        return ref_edge_list.list();
+      }
+    }
+    return {};
+  };
+
+  auto get_edge_list_from_name = [](const std::string& name, const auto& connectivity) -> Array<const EdgeId> {
+    for (size_t i = 0; i < connectivity.template numberOfRefItemList<ItemType::edge>(); ++i) {
+      const auto& ref_edge_list = connectivity.template refItemList<ItemType::edge>(i);
+      const RefId ref_id        = ref_edge_list.refId();
+      if (ref_id.tagName() == name) {
+        return ref_edge_list.list();
+      }
+    }
+    return {};
+  };
+
+  SECTION("1D")
+  {
+    static constexpr size_t Dimension = 1;
+
+    using ConnectivityType = Connectivity<Dimension>;
+    using MeshType         = Mesh<ConnectivityType>;
+
+    SECTION("unordered 1d")
+    {
+      std::shared_ptr p_mesh = MeshDataBaseForTests::get().unordered1DMesh();
+      const MeshType& mesh   = *p_mesh;
+
+      const ConnectivityType& connectivity = mesh.connectivity();
+
+      {
+        const std::set<size_t> tag_set = {3};
+
+        for (auto tag : tag_set) {
+          NumberedInterfaceDescriptor numbered_interface_descriptor(tag);
+          const auto& edge_interface = getMeshEdgeInterface(mesh, numbered_interface_descriptor);
+
+          auto edge_list = get_edge_list_from_tag(tag, connectivity);
+          REQUIRE(is_same(edge_interface.edgeList(), edge_list));
+        }
+      }
+
+      {
+        const std::set<std::string> name_set = {"INTERFACE"};
+
+        for (const auto& name : name_set) {
+          NamedInterfaceDescriptor named_interface_descriptor(name);
+          const auto& edge_interface = getMeshEdgeInterface(mesh, named_interface_descriptor);
+
+          auto edge_list = get_edge_list_from_name(name, connectivity);
+          REQUIRE(is_same(edge_interface.edgeList(), edge_list));
+        }
+      }
+    }
+  }
+
+  SECTION("2D")
+  {
+    static constexpr size_t Dimension = 2;
+
+    using ConnectivityType = Connectivity<Dimension>;
+    using MeshType         = Mesh<ConnectivityType>;
+
+    SECTION("hybrid 2d")
+    {
+      std::shared_ptr p_mesh = MeshDataBaseForTests::get().hybrid2DMesh();
+      const MeshType& mesh   = *p_mesh;
+
+      const ConnectivityType& connectivity = mesh.connectivity();
+
+      {
+        const std::set<size_t> tag_set = {5};
+
+        for (auto tag : tag_set) {
+          NumberedInterfaceDescriptor numbered_interface_descriptor(tag);
+          const auto& edge_interface = getMeshEdgeInterface(mesh, numbered_interface_descriptor);
+
+          auto edge_list = get_edge_list_from_tag(tag, connectivity);
+          REQUIRE(is_same(edge_interface.edgeList(), edge_list));
+        }
+      }
+
+      {
+        const std::set<std::string> name_set = {"INTERFACE"};
+
+        for (const auto& name : name_set) {
+          NamedInterfaceDescriptor numbered_interface_descriptor(name);
+          const auto& edge_interface = getMeshEdgeInterface(mesh, numbered_interface_descriptor);
+
+          auto edge_list = get_edge_list_from_name(name, connectivity);
+          REQUIRE(is_same(edge_interface.edgeList(), edge_list));
+        }
+      }
+    }
+  }
+
+  SECTION("3D")
+  {
+    static constexpr size_t Dimension = 3;
+
+    using ConnectivityType = Connectivity<Dimension>;
+    using MeshType         = Mesh<ConnectivityType>;
+
+    SECTION("hybrid 3d")
+    {
+      std::shared_ptr p_mesh = MeshDataBaseForTests::get().hybrid3DMesh();
+      const MeshType& mesh   = *p_mesh;
+
+      const ConnectivityType& connectivity = mesh.connectivity();
+
+      {
+        const std::set<size_t> tag_set = {55, 56};
+
+        for (auto tag : tag_set) {
+          NumberedInterfaceDescriptor numbered_interface_descriptor(tag);
+          const auto& edge_interface = getMeshEdgeInterface(mesh, numbered_interface_descriptor);
+
+          auto edge_list = get_edge_list_from_tag(tag, connectivity);
+          REQUIRE(is_same(edge_interface.edgeList(), edge_list));
+        }
+      }
+
+      {
+        const std::set<std::string> name_set = {"INTERFACE1", "INTERFACE2"};
+
+        for (const auto& name : name_set) {
+          NamedInterfaceDescriptor numbered_interface_descriptor(name);
+          const auto& edge_interface = getMeshEdgeInterface(mesh, numbered_interface_descriptor);
+
+          auto edge_list = get_edge_list_from_name(name, connectivity);
+          REQUIRE(is_same(edge_interface.edgeList(), edge_list));
+        }
+      }
+    }
+  }
+
+  SECTION("errors")
+  {
+    SECTION("cannot find interface")
+    {
+      static constexpr size_t Dimension = 3;
+
+      using ConnectivityType = Connectivity<Dimension>;
+      using MeshType         = Mesh<ConnectivityType>;
+
+      std::shared_ptr p_mesh = MeshDataBaseForTests::get().hybrid3DMesh();
+      const MeshType& mesh   = *p_mesh;
+
+      NamedInterfaceDescriptor named_interface_descriptor("invalid_interface");
+
+      REQUIRE_THROWS_WITH(getMeshEdgeInterface(mesh, named_interface_descriptor),
+                          "error: cannot find edge list with name \"invalid_interface\"");
+    }
+  }
+}
diff --git a/tests/test_MeshFaceInterface.cpp b/tests/test_MeshFaceInterface.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..503ae0a1fe159fa4c55b8760abdc05a19c2e2370
--- /dev/null
+++ b/tests/test_MeshFaceInterface.cpp
@@ -0,0 +1,238 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+
+#include <mesh/Connectivity.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshFaceInterface.hpp>
+#include <mesh/NamedInterfaceDescriptor.hpp>
+#include <mesh/NumberedInterfaceDescriptor.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("MeshFaceInterface", "[mesh]")
+{
+  auto is_same = [](const auto& a, const auto& b) -> bool {
+    if (a.size() > 0 and b.size() > 0) {
+      return (a.size() == b.size()) and (&(a[0]) == &(b[0]));
+    } else {
+      return (a.size() == b.size());
+    }
+  };
+
+  auto get_face_list_from_tag = [](const size_t tag, const auto& connectivity) -> Array<const FaceId> {
+    for (size_t i = 0; i < connectivity.template numberOfRefItemList<ItemType::face>(); ++i) {
+      const auto& ref_face_list = connectivity.template refItemList<ItemType::face>(i);
+      const RefId ref_id        = ref_face_list.refId();
+      if (ref_id.tagNumber() == tag) {
+        return ref_face_list.list();
+      }
+    }
+    return {};
+  };
+
+  auto get_face_list_from_name = [](const std::string& name, const auto& connectivity) -> Array<const FaceId> {
+    for (size_t i = 0; i < connectivity.template numberOfRefItemList<ItemType::face>(); ++i) {
+      const auto& ref_face_list = connectivity.template refItemList<ItemType::face>(i);
+      const RefId ref_id        = ref_face_list.refId();
+      if (ref_id.tagName() == name) {
+        return ref_face_list.list();
+      }
+    }
+    return {};
+  };
+
+  SECTION("1D")
+  {
+    static constexpr size_t Dimension = 1;
+
+    using ConnectivityType = Connectivity<Dimension>;
+    using MeshType         = Mesh<ConnectivityType>;
+
+    SECTION("unordered 1d")
+    {
+      std::shared_ptr p_mesh = MeshDataBaseForTests::get().unordered1DMesh();
+      const MeshType& mesh   = *p_mesh;
+
+      const ConnectivityType& connectivity = mesh.connectivity();
+
+      {
+        const std::set<size_t> tag_set = {3};
+
+        for (auto tag : tag_set) {
+          NumberedInterfaceDescriptor numbered_interface_descriptor(tag);
+          const auto& face_interface = getMeshFaceInterface(mesh, numbered_interface_descriptor);
+
+          auto face_list = get_face_list_from_tag(tag, connectivity);
+          REQUIRE(is_same(face_interface.faceList(), face_list));
+        }
+      }
+
+      {
+        const std::set<std::string> name_set = {"INTERFACE"};
+
+        for (const auto& name : name_set) {
+          NamedInterfaceDescriptor named_interface_descriptor(name);
+          const auto& face_interface = getMeshFaceInterface(mesh, named_interface_descriptor);
+
+          auto face_list = get_face_list_from_name(name, connectivity);
+          REQUIRE(is_same(face_interface.faceList(), face_list));
+        }
+      }
+    }
+  }
+
+  SECTION("2D")
+  {
+    static constexpr size_t Dimension = 2;
+
+    using ConnectivityType = Connectivity<Dimension>;
+    using MeshType         = Mesh<ConnectivityType>;
+
+    SECTION("hybrid 2d")
+    {
+      std::shared_ptr p_mesh = MeshDataBaseForTests::get().hybrid2DMesh();
+      const MeshType& mesh   = *p_mesh;
+
+      const ConnectivityType& connectivity = mesh.connectivity();
+
+      {
+        const std::set<size_t> tag_set = {5};
+
+        for (auto tag : tag_set) {
+          NumberedInterfaceDescriptor numbered_interface_descriptor(tag);
+          const auto& face_interface = getMeshFaceInterface(mesh, numbered_interface_descriptor);
+
+          auto face_list = get_face_list_from_tag(tag, connectivity);
+          REQUIRE(is_same(face_interface.faceList(), face_list));
+        }
+      }
+
+      {
+        const std::set<std::string> name_set = {"INTERFACE"};
+
+        for (const auto& name : name_set) {
+          NamedInterfaceDescriptor numbered_interface_descriptor(name);
+          const auto& face_interface = getMeshFaceInterface(mesh, numbered_interface_descriptor);
+
+          auto face_list = get_face_list_from_name(name, connectivity);
+          REQUIRE(is_same(face_interface.faceList(), face_list));
+        }
+      }
+    }
+  }
+
+  SECTION("3D")
+  {
+    static constexpr size_t Dimension = 3;
+
+    using ConnectivityType = Connectivity<Dimension>;
+    using MeshType         = Mesh<ConnectivityType>;
+
+    SECTION("hybrid 3d")
+    {
+      std::shared_ptr p_mesh = MeshDataBaseForTests::get().hybrid3DMesh();
+      const MeshType& mesh   = *p_mesh;
+
+      const ConnectivityType& connectivity = mesh.connectivity();
+
+      {
+        const std::set<size_t> tag_set = {55, 56};
+
+        for (auto tag : tag_set) {
+          NumberedInterfaceDescriptor numbered_interface_descriptor(tag);
+          const auto& face_interface = getMeshFaceInterface(mesh, numbered_interface_descriptor);
+
+          auto face_list = get_face_list_from_tag(tag, connectivity);
+          REQUIRE(is_same(face_interface.faceList(), face_list));
+        }
+      }
+
+      {
+        const std::set<std::string> name_set = {"INTERFACE1", "INTERFACE2"};
+
+        for (const auto& name : name_set) {
+          NamedInterfaceDescriptor numbered_interface_descriptor(name);
+          const auto& face_interface = getMeshFaceInterface(mesh, numbered_interface_descriptor);
+
+          auto face_list = get_face_list_from_name(name, connectivity);
+          REQUIRE(is_same(face_interface.faceList(), face_list));
+        }
+      }
+    }
+  }
+
+  SECTION("errors")
+  {
+    SECTION("cannot find interface")
+    {
+      static constexpr size_t Dimension = 3;
+
+      using ConnectivityType = Connectivity<Dimension>;
+      using MeshType         = Mesh<ConnectivityType>;
+
+      std::shared_ptr p_mesh = MeshDataBaseForTests::get().hybrid3DMesh();
+      const MeshType& mesh   = *p_mesh;
+
+      NamedInterfaceDescriptor named_interface_descriptor("invalid_interface");
+
+      REQUIRE_THROWS_WITH(getMeshFaceInterface(mesh, named_interface_descriptor),
+                          "error: cannot find face list with name \"invalid_interface\"");
+    }
+
+    SECTION("surface is inside")
+    {
+      SECTION("1D")
+      {
+        static constexpr size_t Dimension = 1;
+
+        using ConnectivityType = Connectivity<Dimension>;
+        using MeshType         = Mesh<ConnectivityType>;
+
+        std::shared_ptr p_mesh = MeshDataBaseForTests::get().unordered1DMesh();
+        const MeshType& mesh   = *p_mesh;
+
+        NamedInterfaceDescriptor named_interface_descriptor("XMIN");
+
+        REQUIRE_THROWS_WITH(getMeshFaceInterface(mesh, named_interface_descriptor),
+                            "error: invalid interface \"XMIN\": boundary faces cannot be used to define mesh "
+                            "interfaces");
+      }
+
+      SECTION("2D")
+      {
+        static constexpr size_t Dimension = 2;
+
+        using ConnectivityType = Connectivity<Dimension>;
+        using MeshType         = Mesh<ConnectivityType>;
+
+        std::shared_ptr p_mesh = MeshDataBaseForTests::get().hybrid2DMesh();
+        const MeshType& mesh   = *p_mesh;
+
+        NamedInterfaceDescriptor named_interface_descriptor("XMIN");
+
+        REQUIRE_THROWS_WITH(getMeshFaceInterface(mesh, named_interface_descriptor),
+                            "error: invalid interface \"XMIN\": boundary faces cannot be used to define mesh "
+                            "interfaces");
+      }
+
+      SECTION("3D")
+      {
+        static constexpr size_t Dimension = 3;
+
+        using ConnectivityType = Connectivity<Dimension>;
+        using MeshType         = Mesh<ConnectivityType>;
+
+        std::shared_ptr p_mesh = MeshDataBaseForTests::get().hybrid3DMesh();
+        const MeshType& mesh   = *p_mesh;
+
+        NamedInterfaceDescriptor named_interface_descriptor("XMIN");
+
+        REQUIRE_THROWS_WITH(getMeshFaceInterface(mesh, named_interface_descriptor),
+                            "error: invalid interface \"XMIN\": boundary faces cannot be used to define mesh "
+                            "interfaces");
+      }
+    }
+  }
+}
diff --git a/tests/test_MeshNodeBoundary.cpp b/tests/test_MeshNodeBoundary.cpp
index c09276cee007b7ac073dae1aae80760c6e887d3c..aab1cfd500a78b0835ce6a59c62da6f27b1b2cd6 100644
--- a/tests/test_MeshNodeBoundary.cpp
+++ b/tests/test_MeshNodeBoundary.cpp
@@ -345,8 +345,7 @@ TEST_CASE("MeshNodeBoundary", "[mesh]")
         NamedBoundaryDescriptor named_boundary_descriptor("INTERFACE1");
 
         REQUIRE_THROWS_WITH(getMeshNodeBoundary(mesh, named_boundary_descriptor),
-                            "error: invalid boundary \"INTERFACE1(55)\": inner faces cannot be used to define mesh "
-                            "boundaries");
+                            Catch::Matchers::StartsWith("error: invalid boundary \"INTERFACE1(55)\": inner"));
       }
     }
   }
diff --git a/tests/test_MeshNodeInterface.cpp b/tests/test_MeshNodeInterface.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d74080e3fa14541e7debccc4c0ee1fd2c8db76eb
--- /dev/null
+++ b/tests/test_MeshNodeInterface.cpp
@@ -0,0 +1,237 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+
+#include <mesh/Connectivity.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshNodeInterface.hpp>
+#include <mesh/NamedInterfaceDescriptor.hpp>
+#include <mesh/NumberedInterfaceDescriptor.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("MeshNodeInterface", "[mesh]")
+{
+  auto is_same = [](const auto& a, const auto& b) -> bool {
+    if (a.size() > 0 and b.size() > 0) {
+      return (a.size() == b.size()) and (&(a[0]) == &(b[0]));
+    } else {
+      return (a.size() == b.size());
+    }
+  };
+
+  auto get_node_list_from_tag = [](const size_t tag, const auto& connectivity) -> Array<const NodeId> {
+    for (size_t i = 0; i < connectivity.template numberOfRefItemList<ItemType::node>(); ++i) {
+      const auto& ref_node_list = connectivity.template refItemList<ItemType::node>(i);
+      const RefId ref_id        = ref_node_list.refId();
+      if (ref_id.tagNumber() == tag) {
+        return ref_node_list.list();
+      }
+    }
+    return {};
+  };
+
+  auto get_node_list_from_name = [](const std::string& name, const auto& connectivity) -> Array<const NodeId> {
+    for (size_t i = 0; i < connectivity.template numberOfRefItemList<ItemType::node>(); ++i) {
+      const auto& ref_node_list = connectivity.template refItemList<ItemType::node>(i);
+      const RefId ref_id        = ref_node_list.refId();
+      if (ref_id.tagName() == name) {
+        return ref_node_list.list();
+      }
+    }
+    return {};
+  };
+
+  SECTION("1D")
+  {
+    static constexpr size_t Dimension = 1;
+
+    using ConnectivityType = Connectivity<Dimension>;
+    using MeshType         = Mesh<ConnectivityType>;
+
+    SECTION("unordered 1d")
+    {
+      std::shared_ptr p_mesh = MeshDataBaseForTests::get().unordered1DMesh();
+      const MeshType& mesh   = *p_mesh;
+
+      const ConnectivityType& connectivity = mesh.connectivity();
+
+      {
+        const std::set<size_t> tag_set = {3};
+
+        for (auto tag : tag_set) {
+          NumberedInterfaceDescriptor numbered_interface_descriptor(tag);
+          const auto& node_interface = getMeshNodeInterface(mesh, numbered_interface_descriptor);
+
+          auto node_list = get_node_list_from_tag(tag, connectivity);
+          REQUIRE(is_same(node_interface.nodeList(), node_list));
+        }
+      }
+
+      {
+        const std::set<std::string> name_set = {"INTERFACE"};
+
+        for (const auto& name : name_set) {
+          NamedInterfaceDescriptor named_interface_descriptor(name);
+          const auto& node_interface = getMeshNodeInterface(mesh, named_interface_descriptor);
+
+          auto node_list = get_node_list_from_name(name, connectivity);
+          REQUIRE(is_same(node_interface.nodeList(), node_list));
+        }
+      }
+    }
+  }
+
+  SECTION("2D")
+  {
+    static constexpr size_t Dimension = 2;
+
+    using ConnectivityType = Connectivity<Dimension>;
+    using MeshType         = Mesh<ConnectivityType>;
+
+    SECTION("hybrid 2d")
+    {
+      std::shared_ptr p_mesh = MeshDataBaseForTests::get().hybrid2DMesh();
+      const MeshType& mesh   = *p_mesh;
+
+      const ConnectivityType& connectivity = mesh.connectivity();
+
+      {
+        const std::set<size_t> tag_set = {5};
+
+        for (auto tag : tag_set) {
+          NumberedInterfaceDescriptor numbered_interface_descriptor(tag);
+          const auto& node_interface = getMeshNodeInterface(mesh, numbered_interface_descriptor);
+
+          auto node_list = get_node_list_from_tag(tag, connectivity);
+          REQUIRE(is_same(node_interface.nodeList(), node_list));
+        }
+      }
+
+      {
+        const std::set<std::string> name_set = {"INTERFACE"};
+
+        for (const auto& name : name_set) {
+          NamedInterfaceDescriptor numbered_interface_descriptor(name);
+          const auto& node_interface = getMeshNodeInterface(mesh, numbered_interface_descriptor);
+
+          auto node_list = get_node_list_from_name(name, connectivity);
+          REQUIRE(is_same(node_interface.nodeList(), node_list));
+        }
+      }
+    }
+  }
+
+  SECTION("3D")
+  {
+    static constexpr size_t Dimension = 3;
+
+    using ConnectivityType = Connectivity<Dimension>;
+    using MeshType         = Mesh<ConnectivityType>;
+
+    SECTION("hybrid 3d")
+    {
+      std::shared_ptr p_mesh = MeshDataBaseForTests::get().hybrid3DMesh();
+      const MeshType& mesh   = *p_mesh;
+
+      const ConnectivityType& connectivity = mesh.connectivity();
+
+      {
+        const std::set<size_t> tag_set = {55, 56};
+
+        for (auto tag : tag_set) {
+          NumberedInterfaceDescriptor numbered_interface_descriptor(tag);
+          const auto& node_interface = getMeshNodeInterface(mesh, numbered_interface_descriptor);
+
+          auto node_list = get_node_list_from_tag(tag, connectivity);
+          REQUIRE(is_same(node_interface.nodeList(), node_list));
+        }
+      }
+
+      {
+        const std::set<std::string> name_set = {"INTERFACE1", "INTERFACE2"};
+
+        for (const auto& name : name_set) {
+          NamedInterfaceDescriptor numbered_interface_descriptor(name);
+          const auto& node_interface = getMeshNodeInterface(mesh, numbered_interface_descriptor);
+
+          auto node_list = get_node_list_from_name(name, connectivity);
+          REQUIRE(is_same(node_interface.nodeList(), node_list));
+        }
+      }
+    }
+  }
+
+  SECTION("errors")
+  {
+    SECTION("cannot find interface")
+    {
+      static constexpr size_t Dimension = 3;
+
+      using ConnectivityType = Connectivity<Dimension>;
+      using MeshType         = Mesh<ConnectivityType>;
+
+      std::shared_ptr p_mesh = MeshDataBaseForTests::get().hybrid3DMesh();
+      const MeshType& mesh   = *p_mesh;
+
+      NamedInterfaceDescriptor named_interface_descriptor("invalid_interface");
+
+      REQUIRE_THROWS_WITH(getMeshNodeInterface(mesh, named_interface_descriptor),
+                          "error: cannot find node list with name \"invalid_interface\"");
+    }
+
+    SECTION("surface is inside")
+    {
+      SECTION("1D")
+      {
+        static constexpr size_t Dimension = 1;
+
+        using ConnectivityType = Connectivity<Dimension>;
+        using MeshType         = Mesh<ConnectivityType>;
+
+        std::shared_ptr p_mesh = MeshDataBaseForTests::get().unordered1DMesh();
+        const MeshType& mesh   = *p_mesh;
+
+        NamedInterfaceDescriptor named_interface_descriptor("XMIN");
+
+        REQUIRE_THROWS_WITH(getMeshNodeInterface(mesh, named_interface_descriptor),
+                            "error: invalid interface \"XMIN(1)\": boundary nodes cannot be used to define mesh "
+                            "interfaces");
+      }
+
+      SECTION("2D")
+      {
+        static constexpr size_t Dimension = 2;
+
+        using ConnectivityType = Connectivity<Dimension>;
+        using MeshType         = Mesh<ConnectivityType>;
+
+        std::shared_ptr p_mesh = MeshDataBaseForTests::get().hybrid2DMesh();
+        const MeshType& mesh   = *p_mesh;
+
+        NamedInterfaceDescriptor named_interface_descriptor("XMIN");
+
+        REQUIRE_THROWS_WITH(getMeshNodeInterface(mesh, named_interface_descriptor),
+                            "error: invalid interface \"XMIN(1)\": boundary nodes cannot be used to define mesh "
+                            "interfaces");
+      }
+
+      SECTION("3D")
+      {
+        static constexpr size_t Dimension = 3;
+
+        using ConnectivityType = Connectivity<Dimension>;
+        using MeshType         = Mesh<ConnectivityType>;
+
+        std::shared_ptr p_mesh = MeshDataBaseForTests::get().hybrid3DMesh();
+        const MeshType& mesh   = *p_mesh;
+
+        NamedInterfaceDescriptor named_interface_descriptor("ZMAX");
+
+        REQUIRE_THROWS_WITH(getMeshNodeInterface(mesh, named_interface_descriptor),
+                            Catch::Matchers::StartsWith("error: invalid interface \"ZMAX(24)\": boundary"));
+      }
+    }
+  }
+}
diff --git a/tests/test_RefItemList.cpp b/tests/test_RefItemList.cpp
index 147d6543579bfeafb2ff12904dcc1935c7fbd451..da51f6a5e0f9ff55b564800da837544ebf30cb65 100644
--- a/tests/test_RefItemList.cpp
+++ b/tests/test_RefItemList.cpp
@@ -12,18 +12,18 @@ TEST_CASE("RefItemList", "[mesh]")
     const Array<NodeId> node_id_array = convert_to_array(std::vector<NodeId>{1, 3, 7, 2, 4, 11});
     const RefId ref_id{3, "my_reference"};
 
-    RefItemList<ItemType::node> ref_node_list{ref_id, node_id_array, true};
+    RefItemList<ItemType::node> ref_node_list{ref_id, node_id_array, RefItemListBase::Type::boundary};
     REQUIRE(ref_node_list.refId() == ref_id);
     REQUIRE(ref_node_list.list().size() == node_id_array.size());
     REQUIRE(&(ref_node_list.list()[0]) == &(node_id_array[0]));
-    REQUIRE(ref_node_list.isBoundary() == true);
+    REQUIRE(ref_node_list.type() == RefItemListBase::Type::boundary);
 
     {
       RefItemList copy_ref_node_list{ref_node_list};
       REQUIRE(copy_ref_node_list.refId() == ref_id);
       REQUIRE(copy_ref_node_list.list().size() == node_id_array.size());
       REQUIRE(&(copy_ref_node_list.list()[0]) == &(node_id_array[0]));
-      REQUIRE(copy_ref_node_list.isBoundary() == true);
+      REQUIRE(copy_ref_node_list.type() == RefItemListBase::Type::boundary);
     }
 
     {
@@ -32,14 +32,14 @@ TEST_CASE("RefItemList", "[mesh]")
       REQUIRE(affect_ref_node_list.refId() == ref_id);
       REQUIRE(affect_ref_node_list.list().size() == node_id_array.size());
       REQUIRE(&(affect_ref_node_list.list()[0]) == &(node_id_array[0]));
-      REQUIRE(affect_ref_node_list.isBoundary() == true);
+      REQUIRE(affect_ref_node_list.type() == RefItemListBase::Type::boundary);
 
       RefItemList<ItemType::node> move_ref_node_list;
       move_ref_node_list = std::move(affect_ref_node_list);
       REQUIRE(move_ref_node_list.refId() == ref_id);
       REQUIRE(move_ref_node_list.list().size() == node_id_array.size());
       REQUIRE(&(move_ref_node_list.list()[0]) == &(node_id_array[0]));
-      REQUIRE(move_ref_node_list.isBoundary() == true);
+      REQUIRE(move_ref_node_list.type() == RefItemListBase::Type::boundary);
     }
   }
 
@@ -48,18 +48,18 @@ TEST_CASE("RefItemList", "[mesh]")
     const Array<EdgeId> edge_id_array = convert_to_array(std::vector<EdgeId>{1, 3, 7, 2, 4, 11});
     const RefId ref_id{3, "my_reference"};
 
-    RefItemList<ItemType::edge> ref_edge_list{ref_id, edge_id_array, false};
+    RefItemList<ItemType::edge> ref_edge_list{ref_id, edge_id_array, RefItemListBase::Type::interface};
     REQUIRE(ref_edge_list.refId() == ref_id);
     REQUIRE(ref_edge_list.list().size() == edge_id_array.size());
     REQUIRE(&(ref_edge_list.list()[0]) == &(edge_id_array[0]));
-    REQUIRE(ref_edge_list.isBoundary() == false);
+    REQUIRE(ref_edge_list.type() == RefItemListBase::Type::interface);
 
     {
       RefItemList copy_ref_edge_list{ref_edge_list};
       REQUIRE(copy_ref_edge_list.refId() == ref_id);
       REQUIRE(copy_ref_edge_list.list().size() == edge_id_array.size());
       REQUIRE(&(copy_ref_edge_list.list()[0]) == &(edge_id_array[0]));
-      REQUIRE(copy_ref_edge_list.isBoundary() == false);
+      REQUIRE(copy_ref_edge_list.type() == RefItemListBase::Type::interface);
     }
 
     {
@@ -68,14 +68,14 @@ TEST_CASE("RefItemList", "[mesh]")
       REQUIRE(affect_ref_edge_list.refId() == ref_id);
       REQUIRE(affect_ref_edge_list.list().size() == edge_id_array.size());
       REQUIRE(&(affect_ref_edge_list.list()[0]) == &(edge_id_array[0]));
-      REQUIRE(affect_ref_edge_list.isBoundary() == false);
+      REQUIRE(affect_ref_edge_list.type() == RefItemListBase::Type::interface);
 
       RefItemList<ItemType::edge> move_ref_edge_list;
       move_ref_edge_list = std::move(affect_ref_edge_list);
       REQUIRE(move_ref_edge_list.refId() == ref_id);
       REQUIRE(move_ref_edge_list.list().size() == edge_id_array.size());
       REQUIRE(&(move_ref_edge_list.list()[0]) == &(edge_id_array[0]));
-      REQUIRE(move_ref_edge_list.isBoundary() == false);
+      REQUIRE(move_ref_edge_list.type() == RefItemListBase::Type::interface);
     }
   }
 
@@ -84,18 +84,18 @@ TEST_CASE("RefItemList", "[mesh]")
     const Array<FaceId> face_id_array = convert_to_array(std::vector<FaceId>{1, 3, 7, 2, 4, 11});
     const RefId ref_id{3, "my_reference"};
 
-    RefItemList<ItemType::face> ref_face_list{ref_id, face_id_array, true};
+    RefItemList<ItemType::face> ref_face_list{ref_id, face_id_array, RefItemListBase::Type::interface};
     REQUIRE(ref_face_list.refId() == ref_id);
     REQUIRE(ref_face_list.list().size() == face_id_array.size());
     REQUIRE(&(ref_face_list.list()[0]) == &(face_id_array[0]));
-    REQUIRE(ref_face_list.isBoundary() == true);
+    REQUIRE(ref_face_list.type() == RefItemListBase::Type::interface);
 
     {
       RefItemList copy_ref_face_list{ref_face_list};
       REQUIRE(copy_ref_face_list.refId() == ref_id);
       REQUIRE(copy_ref_face_list.list().size() == face_id_array.size());
       REQUIRE(&(copy_ref_face_list.list()[0]) == &(face_id_array[0]));
-      REQUIRE(copy_ref_face_list.isBoundary() == true);
+      REQUIRE(copy_ref_face_list.type() == RefItemListBase::Type::interface);
     }
 
     {
@@ -104,14 +104,14 @@ TEST_CASE("RefItemList", "[mesh]")
       REQUIRE(affect_ref_face_list.refId() == ref_id);
       REQUIRE(affect_ref_face_list.list().size() == face_id_array.size());
       REQUIRE(&(affect_ref_face_list.list()[0]) == &(face_id_array[0]));
-      REQUIRE(affect_ref_face_list.isBoundary() == true);
+      REQUIRE(affect_ref_face_list.type() == RefItemListBase::Type::interface);
 
       RefItemList<ItemType::face> move_ref_face_list;
       move_ref_face_list = std::move(affect_ref_face_list);
       REQUIRE(move_ref_face_list.refId() == ref_id);
       REQUIRE(move_ref_face_list.list().size() == face_id_array.size());
       REQUIRE(&(move_ref_face_list.list()[0]) == &(face_id_array[0]));
-      REQUIRE(move_ref_face_list.isBoundary() == true);
+      REQUIRE(move_ref_face_list.type() == RefItemListBase::Type::interface);
     }
   }
 
@@ -120,18 +120,18 @@ TEST_CASE("RefItemList", "[mesh]")
     const Array<CellId> cell_id_array = convert_to_array(std::vector<CellId>{1, 3, 7, 2, 4, 11});
     const RefId ref_id{3, "my_reference"};
 
-    RefItemList<ItemType::cell> ref_cell_list{ref_id, cell_id_array, false};
+    RefItemList<ItemType::cell> ref_cell_list{ref_id, cell_id_array, RefItemListBase::Type::set};
     REQUIRE(ref_cell_list.refId() == ref_id);
     REQUIRE(ref_cell_list.list().size() == cell_id_array.size());
     REQUIRE(&(ref_cell_list.list()[0]) == &(cell_id_array[0]));
-    REQUIRE(ref_cell_list.isBoundary() == false);
+    REQUIRE(ref_cell_list.type() == RefItemListBase::Type::set);
 
     {
       RefItemList copy_ref_cell_list{ref_cell_list};
       REQUIRE(copy_ref_cell_list.refId() == ref_id);
       REQUIRE(copy_ref_cell_list.list().size() == cell_id_array.size());
       REQUIRE(&(copy_ref_cell_list.list()[0]) == &(cell_id_array[0]));
-      REQUIRE(copy_ref_cell_list.isBoundary() == false);
+      REQUIRE(copy_ref_cell_list.type() == RefItemListBase::Type::set);
     }
 
     {
@@ -140,14 +140,14 @@ TEST_CASE("RefItemList", "[mesh]")
       REQUIRE(affect_ref_cell_list.refId() == ref_id);
       REQUIRE(affect_ref_cell_list.list().size() == cell_id_array.size());
       REQUIRE(&(affect_ref_cell_list.list()[0]) == &(cell_id_array[0]));
-      REQUIRE(affect_ref_cell_list.isBoundary() == false);
+      REQUIRE(affect_ref_cell_list.type() == RefItemListBase::Type::set);
 
       RefItemList<ItemType::cell> move_ref_cell_list;
       move_ref_cell_list = std::move(affect_ref_cell_list);
       REQUIRE(move_ref_cell_list.refId() == ref_id);
       REQUIRE(move_ref_cell_list.list().size() == cell_id_array.size());
       REQUIRE(&(move_ref_cell_list.list()[0]) == &(cell_id_array[0]));
-      REQUIRE(move_ref_cell_list.isBoundary() == false);
+      REQUIRE(move_ref_cell_list.type() == RefItemListBase::Type::set);
     }
   }
 }
diff --git a/tools/pgs-pygments.sh b/tools/pgs-pygments.sh
index 168760cc83772fd8f35629cbf999314c320a1c91..e203c31da47697e9c07108734788c44a8e100247 100755
--- a/tools/pgs-pygments.sh
+++ b/tools/pgs-pygments.sh
@@ -1,4 +1,4 @@
-#! /bin/bash
+#! /usr/bin/env bash
 
 arguments=()