diff --git a/core/lib/Drupal/Core/Config/Schema/ArrayElement.php b/core/lib/Drupal/Core/Config/Schema/ArrayElement.php
index f801c28..c8b1a06 100644
--- a/core/lib/Drupal/Core/Config/Schema/ArrayElement.php
+++ b/core/lib/Drupal/Core/Config/Schema/ArrayElement.php
@@ -16,7 +16,7 @@
 /**
  * Defines a generic configuration element that contains multiple properties.
  */
-abstract class ArrayElement extends Element implements IteratorAggregate, ArrayAccess, Countable {
+abstract class ArrayElement extends Element implements IteratorAggregate, ListElementInterface {
 
   /**
    * Parsed elements.
@@ -24,19 +24,6 @@
   protected $elements;
 
   /**
-   * Gets an array of contained elements.
-   *
-   * @return array
-   *   Array of \Drupal\Core\Config\Schema\ArrayElement objects.
-   */
-  protected function getElements() {
-    if (!isset($this->elements)) {
-      $this->elements = $this->parse();
-    }
-    return $this->elements;
-  }
-
-  /**
    * Gets valid configuration data keys.
    *
    * @return array
@@ -55,15 +42,80 @@ protected function getAllKeys() {
   protected abstract function parse();
 
   /**
-   * Implements TypedDataInterface::validate().
+   * {@inheritdoc}
    */
-  public function validate() {
-    foreach ($this->getElements() as $element) {
-      if (!$element->validate()) {
-        return FALSE;
+  public function get($property_name) {
+    $parts = explode('.', $property_name);
+    $root_key = array_shift($parts);
+    $elements = $this->getElements();
+    if (isset($elements[$root_key])) {
+      $element = $elements[$root_key];
+      // If $property_name contained a dot recurse into the keys.
+      while ($element && ($key = array_shift($parts)) !== NULL) {
+        if ($element instanceof ListElementInterface) {
+          $element = $element->get($key);
+        }
+        else {
+          $element = NULL;
+        }
       }
     }
-    return TRUE;
+    if (isset($element)) {
+      return $element;
+    }
+    else {
+      throw new \InvalidArgumentException(String::format("The configuration property @key doesn't exist.", array('@key' => $property_name)));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function set($key, $value) {
+    // Support setting values via typed data objects.
+    if ($value instanceof TypedDataInterface) {
+      $value = $value->getValue();
+    }
+    $this->value[$key] = $value;
+    // Parsed elements must be rebuilt with new values
+    unset($this->elements);
+    // Directly notify ourselves.
+    $this->onChange($key, $value);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getElements() {
+    if (!isset($this->elements)) {
+      $this->elements = $this->parse();
+    }
+    return $this->elements;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isEmpty() {
+    return empty($this->value);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function toArray() {
+    return isset($this->value) ? $this->value : array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onChange($key) {
+    // Notify the parent of changes.
+    if (isset($this->parent)) {
+      $this->parent->onChange($this->name);
+    }
   }
 
   /**
@@ -85,11 +137,7 @@ public function offsetGet($offset) {
    * Implements ArrayAccess::offsetSet().
    */
   public function offsetSet($offset, $value) {
-    if ($value instanceof TypedDataInterface) {
-      $value = $value->getValue();
-    }
-    $this->value[$offset] = $value;
-    unset($this->elements);
+    $this->set($offset, $value);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Config/Schema/Ignore.php b/core/lib/Drupal/Core/Config/Schema/Ignore.php
index 20701b8..d005302 100644
--- a/core/lib/Drupal/Core/Config/Schema/Ignore.php
+++ b/core/lib/Drupal/Core/Config/Schema/Ignore.php
@@ -12,10 +12,4 @@
  */
 class Ignore extends Element {
 
-  /**
-   * {@inheritdoc}.
-   */
-  public function validate() {
-    return TRUE;
-  }
 }
diff --git a/core/lib/Drupal/Core/Config/Schema/ListElementInterface.php b/core/lib/Drupal/Core/Config/Schema/ListElementInterface.php
new file mode 100644
index 0000000..b6a384f
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Schema/ListElementInterface.php
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Config\ListElementInterface.
+ */
+
+namespace Drupal\Core\Config\Schema;
+
+use Drupal\Core\TypedData\TypedDataInterface;
+
+/**
+ * Interface for a list of typed configuration elements.
+ *
+ * A list of typed configuration contains any number of items whose type
+ * will depend on the configuration schema but also on the configuration
+ * data being parsed.
+ *
+ * When implementing this interface which extends Traversable, make sure to list
+ * IteratorAggregate or Iterator before this interface in the implements clause.
+ */
+interface ListElementInterface extends TypedDataInterface, \ArrayAccess, \Countable, \Traversable {
+
+  /**
+   * Determines whether the list contains any non-empty items.
+   *
+   * @return boolean
+   *   TRUE if the list is empty, FALSE otherwise.
+   */
+  public function isEmpty();
+
+  /**
+   * Gets an array of contained elements.
+   *
+   * @return array
+   *   Array of \Drupal\Core\TypedData\TypedDataInterface objects.
+   */
+  public function getElements();
+
+  /**
+   * Gets a element object.
+   *
+   * @param $property_name
+   *   The name of the property to get; e.g., 'title' or 'name'. Nested
+   *   elements can be get using multiple dot delimited names, for example,
+   *   'page.front'.
+   *
+   * @throws \InvalidArgumentException
+   *   If an invalid property name is given.
+   *
+   * @return \Drupal\Core\TypedData\TypedDataInterface
+   *   The property object.
+   */
+  public function get($property_name);
+
+  /**
+   * Replaces the item at the specified position in this list.
+   *
+   * @param int|string $key
+   *   Property name or index of the item to replace.
+   * @param mixed $value
+   *   Value to be stored at the specified position. It can be a raw
+   *   configuration value or \Drupal\Core\TypedData\TypedDataInterface
+   *
+   * @return $this
+   */
+  public function set($key, $value);
+
+  /**
+   * Returns an array of all property values.
+   *
+   * @return array
+   *   An array of property values, keyed by property name.
+   */
+  public function toArray();
+
+  /**
+   * React to changes to a child element.
+   *
+   * Note that this is invoked after any changes have been applied.
+   *
+   * @param $key
+   *   The key of the property which is changed.
+   */
+  public function onChange($key);
+}
diff --git a/core/lib/Drupal/Core/Config/Schema/Mapping.php b/core/lib/Drupal/Core/Config/Schema/Mapping.php
index a85fac4..91623e2 100644
--- a/core/lib/Drupal/Core/Config/Schema/Mapping.php
+++ b/core/lib/Drupal/Core/Config/Schema/Mapping.php
@@ -13,11 +13,17 @@
 /**
  * Defines a mapping configuration element.
  *
- * Wraps configuration data and metadata allowing access to configuration data
- * using the ComplexDataInterface API. This object may contain any number and
- * type of nested properties.
+ * This object may contain any number and type of nested properties and each
+ * property key may have its own definition in the 'mapping' property of the
+ * configuration schema.
+ *
+ * Properties in the configuration value that are not defined in the mapping
+ * will get the 'undefined' data type.
+ *
+ * Read https://drupal.org/node/1905070 for more details about configuration
+ * schema, types and type resolution.
  */
-class Mapping extends ArrayElement implements ComplexDataInterface {
+class Mapping extends ArrayElement {
 
   /**
    * An array of data definitions.
@@ -39,70 +45,13 @@ protected function parse() {
 
   /**
    * {@inheritdoc}
-   *
-   * Since all configuration objects are mappings the function will except a dot
-   * delimited key to access nested values, for example, 'page.front'.
    */
-  public function get($property_name) {
-    $parts = explode('.', $property_name);
-    $root_key = array_shift($parts);
-    $elements = $this->getElements();
-    if (isset($elements[$root_key])) {
-      $element = $elements[$root_key];
-      // If $property_name contained a dot recurse into the keys.
-      while ($element && ($key = array_shift($parts)) !== NULL) {
-        if (method_exists($element, 'get')) {
-          $element = $element->get($key);
-        }
-        else {
-          $element = NULL;
-        }
-      }
-    }
-    if (isset($element)) {
-      return $element;
-    }
-    else {
-      throw new \InvalidArgumentException(String::format("The configuration property @key doesn't exist.", array('@key' => $property_name)));
-    }
-  }
+  public function set($key, $value) {
+    parent::set($key, $value);
+    // We may need to rebuild property definitions.
+    unset($this->propertyDefinitions);
 
-  /**
-   * Implements Drupal\Core\TypedData\ComplexDataInterface::set().
-   */
-  public function set($property_name, $value, $notify = TRUE) {
-    // Set the data into the configuration array but behave according to the
-    // interface specification when we've got a null value.
-    if (isset($value)) {
-      $this->value[$property_name] = $value;
-      $property = $this->get($property_name);
-    }
-    else {
-      // In these objects, when clearing the value, the property is gone.
-      // As this needs to return a property, we get it before we delete it.
-      $property = $this->get($property_name);
-      unset($this->value[$property_name]);
-      $property->setValue($value);
-    }
-    // Notify the parent of any changes.
-    if ($notify && isset($this->parent)) {
-      $this->parent->onChange($this->name);
-    }
-    return $property;
-  }
-
-  /**
-   * Implements Drupal\Core\TypedData\ComplexDataInterface::getProperties().
-   */
-  public function getProperties($include_computed = FALSE) {
-    return $this->getElements();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function toArray() {
-    return $this->getValue();
+    return $this;
   }
 
   /**
@@ -137,21 +86,4 @@ public function getPropertyDefinitions() {
     return $this->propertyDefinitions;
   }
 
-  /**
-   * Implements Drupal\Core\TypedData\ComplexDataInterface::isEmpty().
-   */
-  public function isEmpty() {
-    return empty($this->value);
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::onChange().
-   */
-  public function onChange($property_name) {
-    // Notify the parent of changes.
-    if (isset($this->parent)) {
-      $this->parent->onChange($this->name);
-    }
-  }
-
 }
diff --git a/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php b/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php
index 0836eaf..d4be93c 100644
--- a/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php
+++ b/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php
@@ -107,7 +107,7 @@ protected function checkValue($key, $value) {
     }
     else {
       $errors = array();
-      if (!$element instanceof ArrayElement) {
+      if (!$element instanceof ListElementInterface) {
         $errors[$error_key] = 'Non-scalar value but not defined as an array (such as mapping or sequence).';
       }
 
diff --git a/core/lib/Drupal/Core/Config/Schema/Sequence.php b/core/lib/Drupal/Core/Config/Schema/Sequence.php
index e5b6a35..26576ef 100644
--- a/core/lib/Drupal/Core/Config/Schema/Sequence.php
+++ b/core/lib/Drupal/Core/Config/Schema/Sequence.php
@@ -7,22 +7,19 @@
 
 namespace Drupal\Core\Config\Schema;
 
-use Drupal\Core\TypedData\ListInterface;
-
 /**
  * Defines a configuration element of type Sequence.
+ *
+ * This object may contain any number and type of nested elements that share
+ * a common definition in the 'sequence' property of the configuration schema.
+ *
+ * Read https://drupal.org/node/1905070 for more details about configuration
+ * schema, types and type resolution.
  */
-class Sequence extends ArrayElement implements ListInterface {
+class Sequence extends ArrayElement {
 
   /**
-   * Data definition
-   *
-   * @var \Drupal\Core\TypedData\DataDefinitionInterface
-   */
-  protected $itemDefinition;
-
-  /**
-   * Overrides ArrayElement::parse()
+   * {@inheritdoc}
    */
   protected function parse() {
     // Creates a new data definition object for each item from the generic type
@@ -38,55 +35,4 @@ protected function parse() {
     return $elements;
   }
 
-  /**
-   * Implements Drupal\Core\TypedData\ListInterface::isEmpty().
-   */
-  public function isEmpty() {
-    return empty($this->value);
-  }
-
-  /**
-   * Implements Drupal\Core\TypedData\ListInterface::getItemDefinition().
-   */
-  public function getItemDefinition() {
-    if (!isset($this->itemDefinition)) {
-      $definition = isset($this->definition['sequence'][0]) ? $this->definition['sequence'][0] : array();
-      $this->itemDefinition = $this->buildDataDefinition($definition, NULL);
-    }
-    return $this->itemDefinition;
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ListInterface::onChange().
-   */
-  public function onChange($delta) {
-    // Notify the parent of changes.
-    if (isset($this->parent)) {
-      $this->parent->onChange($this->name);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function get($key) {
-    $elements = $this->getElements();
-    return $elements[$key];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function first() {
-    return $this->get(0);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function set($index, $item) {
-    $this->offsetSet($index, $item);
-    return $this;
-  }
-
 }
diff --git a/core/lib/Drupal/Core/Config/Schema/Undefined.php b/core/lib/Drupal/Core/Config/Schema/Undefined.php
index 1bcf744..6f62b0d 100644
--- a/core/lib/Drupal/Core/Config/Schema/Undefined.php
+++ b/core/lib/Drupal/Core/Config/Schema/Undefined.php
@@ -12,10 +12,4 @@
  */
 class Undefined extends Element {
 
-  /**
-   * {@inheritdoc}.
-   */
-  public function validate() {
-    return isset($this->value);
-  }
 }
diff --git a/core/modules/config/src/Tests/ConfigSchemaTest.php b/core/modules/config/src/Tests/ConfigSchemaTest.php
index 7401247..fc4f9ae 100644
--- a/core/modules/config/src/Tests/ConfigSchemaTest.php
+++ b/core/modules/config/src/Tests/ConfigSchemaTest.php
@@ -280,8 +280,8 @@ function testSchemaData() {
     $this->assertTrue(isset($list['front']) && isset($list['403']) && isset($list['404']), 'Got a list with the right properties for site page data.');
     $this->assertEqual($list['front']->getValue(), 'user', 'Got the right value for page.front data from the list.');
 
-    // And test some ComplexDataInterface methods.
-    $properties = $list->getProperties();
+    // And test some ListElementInterface methods.
+    $properties = $list->getElements();
     $this->assertTrue(count($properties) == 3 && $properties['front'] == $list['front'], 'Got the right properties for site page.');
     $values = $list->toArray();
     $this->assertTrue(count($values) == 3 && $values['front'] == 'user', 'Got the right property values for site page.');
diff --git a/core/modules/config_translation/src/ConfigMapperManager.php b/core/modules/config_translation/src/ConfigMapperManager.php
index 8eb3735..c241485 100644
--- a/core/modules/config_translation/src/ConfigMapperManager.php
+++ b/core/modules/config_translation/src/ConfigMapperManager.php
@@ -10,7 +10,7 @@
 use Drupal\Component\Utility\String;
 use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
 use Drupal\Core\Cache\CacheBackendInterface;
-use Drupal\Core\Config\Schema\ArrayElement;
+use Drupal\Core\Config\Schema\ListElementInterface;
 use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Extension\ThemeHandlerInterface;
@@ -176,7 +176,7 @@ public function hasTranslatable($name) {
   protected function findTranslatable(TypedDataInterface $element) {
     // In case this is a sequence or a mapping check whether any child element
     // is translatable.
-    if ($element instanceof ArrayElement) {
+    if ($element instanceof ListElementInterface) {
       foreach ($element as $child_element) {
         if ($this->findTranslatable($child_element)) {
           return TRUE;
diff --git a/core/modules/config_translation/tests/src/ConfigMapperManagerTest.php b/core/modules/config_translation/tests/src/ConfigMapperManagerTest.php
index d7c47cb..8163638 100644
--- a/core/modules/config_translation/tests/src/ConfigMapperManagerTest.php
+++ b/core/modules/config_translation/tests/src/ConfigMapperManagerTest.php
@@ -170,10 +170,7 @@ protected function getElement(array $definition) {
    *   A nested schema element, containing the passed-in elements.
    */
   protected function getNestedElement(array $elements) {
-    // ConfigMapperManager::findTranslatable() checks for the abstract class
-    // \Drupal\Core\Config\Schema\ArrayElement, but mocking that directly does
-    // not work.
-    $nested_element = $this->getMockBuilder('Drupal\Core\Config\Schema\Mapping')
+    $nested_element = $this->getMockBuilder('Drupal\Core\Config\Schema\ListElementInterface')
       ->disableOriginalConstructor()
       ->getMock();
     $nested_element->expects($this->once())
diff --git a/core/modules/locale/src/LocaleTypedConfig.php b/core/modules/locale/src/LocaleTypedConfig.php
index 36e39a9..a01f50f 100644
--- a/core/modules/locale/src/LocaleTypedConfig.php
+++ b/core/modules/locale/src/LocaleTypedConfig.php
@@ -10,7 +10,7 @@
 use Drupal\Core\TypedData\ContextAwareInterface;
 use Drupal\Core\TypedData\DataDefinitionInterface;
 use Drupal\Core\Config\Schema\Element;
-use Drupal\Core\Config\Schema\ArrayElement;
+use Drupal\Core\Config\Schema\ListElementInterface;
 
 /**
  * Defines the locale configuration wrapper object.
@@ -105,7 +105,7 @@ protected function canTranslate($from_langcode, $to_langcode) {
    *
    * @param mixed $element
    *   Typed configuration element, either \Drupal\Core\Config\Schema\Element or
-   *   \Drupal\Core\Config\Schema\ArrayElement.
+   *   \Drupal\Core\Config\Schema\ListElementInterface.
    * @param array $options
    *   Array with translation options that must contain the keys defined in
    *   \Drupal\locale\LocaleTypedConfig::translateElement()
@@ -116,7 +116,7 @@ protected function canTranslate($from_langcode, $to_langcode) {
    */
   protected function getElementTranslation($element, array $options) {
     $translation = array();
-    if ($element instanceof ArrayElement) {
+    if ($element instanceof ListElementInterface) {
       $translation = $this->getArrayTranslation($element, $options);
     }
     elseif ($this->translateElement($element, $options)) {
@@ -126,9 +126,9 @@ protected function getElementTranslation($element, array $options) {
   }
 
   /**
-   * Gets translated configuration data for an element of type ArrayElement.
+   * Gets translated configuration data for an element of type ListElementInterface.
    *
-   * @param \Drupal\Core\Config\Schema\ArrayElement $element
+   * @param \Drupal\Core\Config\Schema\ListElementInterface $element
    *   Typed configuration array element.
    * @param array $options
    *   Array with translation options that must contain the keys defined in
@@ -137,7 +137,7 @@ protected function getElementTranslation($element, array $options) {
    * @return array
    *   Configuration data translated to the requested language.
    */
-  protected function getArrayTranslation(ArrayElement $element, array $options) {
+  protected function getArrayTranslation(ListElementInterface $element, array $options) {
     $translation = array();
     foreach ($element as $key => $property) {
       $value = $this->getElementTranslation($property, $options);
diff --git a/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php b/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php
index 94ec34d..5befd1a 100644
--- a/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php
+++ b/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php
@@ -81,7 +81,7 @@ function testConfigTranslation() {
 
     // Get translation and check we've only got the site name.
     $translation = $wrapper->getTranslation($langcode);
-    $properties = $translation->getProperties();
+    $properties = $translation->getElements();
     $this->assertEqual(count($properties), 1, 'Got the right number of properties after translation');
 
     // Check the translated site name is displayed.
diff --git a/core/modules/locale/src/Tests/LocaleImportFunctionalTest.php b/core/modules/locale/src/Tests/LocaleImportFunctionalTest.php
index be3c22a..4e02ffd 100644
--- a/core/modules/locale/src/Tests/LocaleImportFunctionalTest.php
+++ b/core/modules/locale/src/Tests/LocaleImportFunctionalTest.php
@@ -297,7 +297,7 @@ function testConfigPoFile() {
     foreach ($config_strings as $config_key => $config_string) {
       $wrapper = $locale_config->get($config_key);
       $translation = $wrapper->getTranslation($langcode);
-      $properties = $translation->getProperties();
+      $properties = $translation->getElements();
       $this->assertEqual(count($properties), 1, 'Got the right number of properties with strict translation');
       $this->assertEqual($properties[$config_string[2]]->getValue(), $config_string[1]);
     }
