diff --git a/core/lib/Drupal/Core/Config/BatchConfigImporter.php b/core/lib/Drupal/Core/Config/BatchConfigImporter.php
new file mode 100644
index 0000000000000000000000000000000000000000..a57a0764c207649a2f46b6f4c8dd56275ca9bdf7
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/BatchConfigImporter.php
@@ -0,0 +1,190 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Config\BatchConfigImporter.
+ */
+
+namespace Drupal\Core\Config;
+
+/**
+ * Defines a batch configuration importer.
+ *
+ * @see \Drupal\Core\Config\ConfigImporter
+ */
+class BatchConfigImporter extends ConfigImporter {
+
+  /**
+   * The total number of extensions to process.
+   *
+   * @var int
+   */
+  protected $totalExtensionsToProcess = 0;
+
+  /**
+   * The total number of configuration objects to process.
+   *
+   * @var int
+   */
+  protected $totalConfigurationToProcess = 0;
+
+  /**
+   * Initializes the config importer in preparation for processing a batch.
+   *
+   * @return array
+   *   An array of method names that to be called by the batch. If there are
+   *   modules or themes to process then an extra step is added.
+   *
+   * @throws ConfigImporterException
+   *   If the configuration is already importing.
+   */
+  public function initialize() {
+    $batch_operations = array();
+    $this->createExtensionChangelist();
+
+    // Ensure that the changes have been validated.
+    $this->validate();
+
+    if (!$this->lock->acquire(static::LOCK_ID)) {
+      // Another process is synchronizing configuration.
+      throw new ConfigImporterException(sprintf('%s is already importing', static::LOCK_ID));
+    }
+
+    $modules = $this->getUnprocessedExtensions('module');
+    foreach (array('install', 'uninstall') as $op) {
+      $this->totalExtensionsToProcess += count($modules[$op]);
+    }
+    $themes = $this->getUnprocessedExtensions('theme');
+    foreach (array('enable', 'disable') as $op) {
+      $this->totalExtensionsToProcess += count($themes[$op]);
+    }
+
+    // We have extensions to process.
+    if ($this->totalExtensionsToProcess > 0) {
+      $batch_operations[] = 'processExtensionBatch';
+    }
+
+    $batch_operations[] = 'processConfigurationBatch';
+    $batch_operations[] = 'finishBatch';
+    return $batch_operations;
+  }
+
+  /**
+   * Processes extensions as a batch operation.
+   *
+   * @param array $context.
+   *   The batch context.
+   */
+  public function processExtensionBatch(array &$context) {
+    $operation = $this->getNextExtensionOperation();
+    if (!empty($operation)) {
+      $this->processExtension($operation['type'], $operation['op'], $operation['name']);
+      $context['message'] = t('Synchronising extensions: @op @name.', array('@op' => $operation['op'], '@name' => $operation['name']));
+      $processed_count = count($this->processedExtensions['module']['install']) + count($this->processedExtensions['module']['uninstall']);
+      $processed_count += count($this->processedExtensions['theme']['disable']) + count($this->processedExtensions['theme']['enable']);
+      $context['finished'] = $processed_count / $this->totalExtensionsToProcess;
+    }
+    else {
+      $context['finished'] = 1;
+    }
+  }
+
+  /**
+   * Processes configuration as a batch operation.
+   *
+   * @param array $context.
+   *   The batch context.
+   */
+  public function processConfigurationBatch(array &$context) {
+    // The first time this is called we need to calculate the total to process.
+    // This involves recalculating the changelist which will ensure that if
+    // extensions have been processed any configuration affected will be taken
+    // into account.
+    if ($this->totalConfigurationToProcess == 0) {
+      $this->storageComparer->reset();
+      foreach (array('delete', 'create', 'update') as $op) {
+        $this->totalConfigurationToProcess += count($this->getUnprocessedConfiguration($op));
+      }
+    }
+    $operation = $this->getNextConfigurationOperation();
+    if (!empty($operation)) {
+      if ($this->checkOp($operation['op'], $operation['name'])) {
+        $this->processConfiguration($operation['op'], $operation['name']);
+      }
+      $context['message'] = t('Synchronizing configuration: @op @name.', array('@op' => $operation['op'], '@name' => $operation['name']));
+      $processed_count = count($this->processedConfiguration['create']) + count($this->processedConfiguration['delete']) + count($this->processedConfiguration['update']);
+      $context['finished'] = $processed_count / $this->totalConfigurationToProcess;
+    }
+    else {
+      $context['finished'] = 1;
+    }
+  }
+
+  /**
+   * Finishes the batch.
+   *
+   * @param array $context.
+   *   The batch context.
+   */
+  public function finishBatch(array &$context) {
+    $this->eventDispatcher->dispatch(ConfigEvents::IMPORT, new ConfigImporterEvent($this));
+    // The import is now complete.
+    $this->lock->release(static::LOCK_ID);
+    $this->reset();
+    $context['message'] = t('Finalising configuration synchronisation.');
+    $context['finished'] = 1;
+  }
+
+  /**
+   * Gets the next extension operation to perform.
+   *
+   * @return array|bool
+   *   An array containing the next operation and extension name to perform it
+   *   on. If there is nothing left to do returns FALSE;
+   */
+  protected function getNextExtensionOperation() {
+    foreach (array('install', 'uninstall') as $op) {
+      $modules = $this->getUnprocessedExtensions('module');
+      if (!empty($modules[$op])) {
+        return array(
+          'op' => $op,
+          'type' => 'module',
+          'name' => array_shift($modules[$op]),
+        );
+      }
+    }
+    foreach (array('enable', 'disable') as $op) {
+      $themes = $this->getUnprocessedExtensions('theme');
+      if (!empty($themes[$op])) {
+        return array(
+          'op' => $op,
+          'type' => 'theme',
+          'name' => array_shift($themes[$op]),
+        );
+      }
+    }
+    return FALSE;
+  }
+
+  /**
+   * Gets the next configuration operation to perform.
+   *
+   * @return array|bool
+   *   An array containing the next operation and configuration name to perform
+   *   it on. If there is nothing left to do returns FALSE;
+   */
+  protected function getNextConfigurationOperation() {
+    // The order configuration operations is processed is important. Deletes
+    // have to come first so that recreates can work.
+    foreach (array('delete', 'create', 'update') as $op) {
+      $config_names = $this->getUnprocessedConfiguration($op);
+      if (!empty($config_names)) {
+        return array(
+          'op' => $op,
+          'name' => array_shift($config_names),
+        );
+      }
+    }
+    return FALSE;
+  }
+}
diff --git a/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php
index 28dd74ed8b21a4161236aa1a7d1cb7a893d15a63..9ddbfb04dc8159a7e90d86e4a3f0a0371cf3300d 100644
--- a/core/lib/Drupal/Core/Config/ConfigImporter.php
+++ b/core/lib/Drupal/Core/Config/ConfigImporter.php
@@ -141,20 +141,6 @@ class ConfigImporter extends DependencySerialization {
    */
   protected $errors = array();
 
-  /**
-   * The total number of extensions to process.
-   *
-   * @var int
-   */
-  protected $totalExtensionsToProcess = 0;
-
-  /**
-   * The total number of configuration objects to process.
-   *
-   * @var int
-   */
-  protected $totalConfigurationToProcess = 0;
-
   /**
    * Constructs a configuration import object.
    *
@@ -450,207 +436,36 @@ public function getUnprocessedExtensions($type) {
    */
   public function import() {
     if ($this->hasUnprocessedConfigurationChanges()) {
-      $sync_steps = $this->initialize();
-
-      foreach ($sync_steps as $step) {
-        $context = array();
-        do {
-          $this->doSyncStep($step, $context);
-        } while ($context['finished'] < 1);
-      }
-    }
-    return $this;
-  }
-
-  /**
-   * Calls a config import step.
-   *
-   * @param string|callable $sync_step
-   *   The step to do. Either a method on the ConfigImporter class or a
-   *   callable.
-   * @param array $context
-   *   A batch context array. If the config importer is not running in a batch
-   *   the only array key that is used is $context['finished']. A process needs
-   *   to set $context['finished'] = 1 when it is done.
-   *
-   * @throws \InvalidArgumentException
-   *   Exception thrown if the $sync_step can not be called.
-   */
-  public function doSyncStep($sync_step, &$context) {
-    if (method_exists($this, $sync_step)) {
-      $this->$sync_step($context);
-    }
-    elseif (is_callable($sync_step)) {
-      call_user_func_array($sync_step, array(&$context));
-    }
-    else {
-      throw new \InvalidArgumentException('Invalid configuration synchronization step');
-    }
-  }
-
-  /**
-   * Initializes the config importer in preparation for processing a batch.
-   *
-   * @return array
-   *   An array of \Drupal\Core\Config\ConfigImporter method names and callables
-   *   that are invoked to complete the import. If there are modules or themes
-   *   to process then an extra step is added.
-   *
-   * @throws \Drupal\Core\Config\ConfigImporterException
-   *   If the configuration is already importing.
-   */
-  public function initialize() {
-    $this->createExtensionChangelist();
-
-    // Ensure that the changes have been validated.
-    $this->validate();
-
-    if (!$this->lock->acquire(static::LOCK_ID)) {
-      // Another process is synchronizing configuration.
-      throw new ConfigImporterException(sprintf('%s is already importing', static::LOCK_ID));
-    }
+      $this->createExtensionChangelist();
 
-    $sync_steps = array();
-    $modules = $this->getUnprocessedExtensions('module');
-    foreach (array('install', 'uninstall') as $op) {
-      $this->totalExtensionsToProcess += count($modules[$op]);
-    }
-    $themes = $this->getUnprocessedExtensions('theme');
-    foreach (array('enable', 'disable') as $op) {
-      $this->totalExtensionsToProcess += count($themes[$op]);
-    }
+      // Ensure that the changes have been validated.
+      $this->validate();
 
-    // We have extensions to process.
-    if ($this->totalExtensionsToProcess > 0) {
-      $sync_steps[] = 'processExtensions';
-    }
-    $sync_steps[] = 'processConfigurations';
-
-    // Allow modules to add new steps to configuration synchronization.
-    $this->moduleHandler->alter('config_import_steps', $sync_steps);
-    $sync_steps[] = 'finish';
-    return $sync_steps;
-  }
+      if (!$this->lock->acquire(static::LOCK_ID)) {
+        // Another process is synchronizing configuration.
+        throw new ConfigImporterException(sprintf('%s is already importing', static::LOCK_ID));
+      }
 
-  /**
-   * Processes extensions as a batch operation.
-   *
-   * @param array $context.
-   *   The batch context.
-   */
-  public function processExtensions(array &$context) {
-    $operation = $this->getNextExtensionOperation();
-    if (!empty($operation)) {
-      $this->processExtension($operation['type'], $operation['op'], $operation['name']);
-      $context['message'] = t('Synchronising extensions: @op @name.', array('@op' => $operation['op'], '@name' => $operation['name']));
-      $processed_count = count($this->processedExtensions['module']['install']) + count($this->processedExtensions['module']['uninstall']);
-      $processed_count += count($this->processedExtensions['theme']['disable']) + count($this->processedExtensions['theme']['enable']);
-      $context['finished'] = $processed_count / $this->totalExtensionsToProcess;
-    }
-    else {
-      $context['finished'] = 1;
-    }
-  }
+      // Process any extension changes before importing configuration.
+      $this->handleExtensions();
 
-  /**
-   * Processes configuration as a batch operation.
-   *
-   * @param array $context.
-   *   The batch context.
-   */
-  public function processConfigurations(array &$context) {
-    // The first time this is called we need to calculate the total to process.
-    // This involves recalculating the changelist which will ensure that if
-    // extensions have been processed any configuration affected will be taken
-    // into account.
-    if ($this->totalConfigurationToProcess == 0) {
-      $this->storageComparer->reset();
+      // First pass deleted, then new, and lastly changed configuration, in order
+      // to handle dependencies correctly.
       foreach (array('delete', 'create', 'update') as $op) {
         foreach ($this->getUnprocessedConfiguration($op) as $name) {
-          $this->totalConfigurationToProcess += count($this->getUnprocessedConfiguration($op));
+          if ($this->checkOp($op, $name)) {
+            $this->processConfiguration($op, $name);
+          }
         }
       }
-    }
-    $operation = $this->getNextConfigurationOperation();
-    if (!empty($operation)) {
-      if ($this->checkOp($operation['op'], $operation['name'])) {
-        $this->processConfiguration($operation['op'], $operation['name']);
-      }
-      $context['message'] = t('Synchronizing configuration: @op @name.', array('@op' => $operation['op'], '@name' => $operation['name']));
-      $processed_count = count($this->processedConfiguration['create']) + count($this->processedConfiguration['delete']) + count($this->processedConfiguration['update']);
-      $context['finished'] = $processed_count / $this->totalConfigurationToProcess;
-    }
-    else {
-      $context['finished'] = 1;
-    }
-  }
-
-  /**
-   * Finishes the batch.
-   *
-   * @param array $context.
-   *   The batch context.
-   */
-  public function finish(array &$context) {
-    $this->eventDispatcher->dispatch(ConfigEvents::IMPORT, new ConfigImporterEvent($this));
-    // The import is now complete.
-    $this->lock->release(static::LOCK_ID);
-    $this->reset();
-    $context['message'] = t('Finalising configuration synchronisation.');
-    $context['finished'] = 1;
-  }
+      // Allow modules to react to a import.
+      $this->eventDispatcher->dispatch(ConfigEvents::IMPORT, new ConfigImporterEvent($this));
 
-  /**
-   * Gets the next extension operation to perform.
-   *
-   * @return array|bool
-   *   An array containing the next operation and extension name to perform it
-   *   on. If there is nothing left to do returns FALSE;
-   */
-  protected function getNextExtensionOperation() {
-    foreach (array('install', 'uninstall') as $op) {
-      $modules = $this->getUnprocessedExtensions('module');
-      if (!empty($modules[$op])) {
-        return array(
-          'op' => $op,
-          'type' => 'module',
-          'name' => array_shift($modules[$op]),
-        );
-      }
-    }
-    foreach (array('enable', 'disable') as $op) {
-      $themes = $this->getUnprocessedExtensions('theme');
-      if (!empty($themes[$op])) {
-        return array(
-          'op' => $op,
-          'type' => 'theme',
-          'name' => array_shift($themes[$op]),
-        );
-      }
+      // The import is now complete.
+      $this->lock->release(static::LOCK_ID);
+      $this->reset();
     }
-    return FALSE;
-  }
-
-  /**
-   * Gets the next configuration operation to perform.
-   *
-   * @return array|bool
-   *   An array containing the next operation and configuration name to perform
-   *   it on. If there is nothing left to do returns FALSE;
-   */
-  protected function getNextConfigurationOperation() {
-    // The order configuration operations is processed is important. Deletes
-    // have to come first so that recreates can work.
-    foreach (array('delete', 'create', 'update') as $op) {
-      $config_names = $this->getUnprocessedConfiguration($op);
-      if (!empty($config_names)) {
-        return array(
-          'op' => $op,
-          'name' => array_shift($config_names),
-        );
-      }
-    }
-    return FALSE;
+    return $this;
   }
 
   /**
@@ -896,6 +711,50 @@ public function getId() {
     return static::LOCK_ID;
   }
 
+  /**
+   * Checks if a configuration object will be updated by the import.
+   *
+   * @param string $config_name
+   *   The configuration object name.
+   *
+   * @return bool
+   *   TRUE if the configuration object will be updated.
+   */
+  protected function hasUpdate($config_name) {
+    return in_array($config_name, $this->getUnprocessedConfiguration('update'));
+  }
+
+  /**
+   * Handle changes to installed modules and themes.
+   */
+  protected function handleExtensions() {
+    $processed_extension = FALSE;
+    foreach (array('install', 'uninstall') as $op) {
+      $modules = $this->getUnprocessedExtensions('module');
+      foreach($modules[$op] as $module) {
+        $processed_extension = TRUE;
+        $this->processExtension('module', $op, $module);
+      }
+    }
+    foreach (array('enable', 'disable') as $op) {
+      $themes = $this->getUnprocessedExtensions('theme');
+      foreach($themes[$op] as $theme) {
+        $processed_extension = TRUE;
+        $this->processExtension('theme', $op, $theme);
+      }
+    }
+
+    if ($processed_extension) {
+      // Recalculate differences as default config could have been imported.
+      $this->storageComparer->reset();
+      $this->processed = $this->storageComparer->getEmptyChangelist();
+      // Modules have been updated. Services etc might have changed.
+      // We don't reinject storage comparer because swapping out the active
+      // store during config import is a complete nonsense.
+      $this->recalculateChangelist = TRUE;
+    }
+  }
+
   /**
    * Gets all the service dependencies from \Drupal.
    *
diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php
index 6c81152abd6dab0fa118a1c4ab66b34d6c982784..907340cc749948a3dff0867504391732a358df70 100644
--- a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php
+++ b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php
@@ -8,7 +8,6 @@
 namespace Drupal\config\Form;
 
 use Drupal\Component\Uuid\UuidInterface;
-use Drupal\Core\Config\ConfigImporter;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Extension\ThemeHandlerInterface;
@@ -16,6 +15,7 @@
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Config\StorageInterface;
 use Drupal\Core\Lock\LockBackendInterface;
+use Drupal\Core\Config\BatchConfigImporter;
 use Drupal\Core\Config\StorageComparer;
 use Drupal\Core\Config\TypedConfigManager;
 use Drupal\Core\Routing\UrlGeneratorInterface;
@@ -243,7 +243,7 @@ public function buildForm(array $form, array &$form_state) {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, array &$form_state) {
-    $config_importer = new ConfigImporter(
+    $config_importer = new BatchConfigImporter(
       $form_state['storage_comparer'],
       $this->eventDispatcher,
       $this->configManager,
@@ -257,7 +257,7 @@ public function submitForm(array &$form, array &$form_state) {
       drupal_set_message($this->t('Another request may be synchronizing configuration already.'));
     }
     else{
-      $sync_steps = $config_importer->initialize();
+      $operations = $config_importer->initialize();
       $batch = array(
         'operations' => array(),
         'finished' => array(get_class($this), 'finishBatch'),
@@ -267,8 +267,8 @@ public function submitForm(array &$form, array &$form_state) {
         'error_message' => t('Configuration synchronization has encountered an error.'),
         'file' => drupal_get_path('module', 'config') . '/config.admin.inc',
       );
-      foreach ($sync_steps as $sync_step) {
-        $batch['operations'][] = array(array(get_class($this), 'processBatch'), array($config_importer, $sync_step));
+      foreach ($operations as $operation) {
+        $batch['operations'][] = array(array(get_class($this), 'processBatch'), array($config_importer, $operation));
       }
 
       batch_set($batch);
@@ -278,20 +278,18 @@ public function submitForm(array &$form, array &$form_state) {
   /**
    * Processes the config import batch and persists the importer.
    *
-   * @param \Drupal\Core\Config\ConfigImporter $config_importer
+   * @param BatchConfigImporter $config_importer
    *   The batch config importer object to persist.
-   * @param string $sync_step
-   *   The synchronisation step to do.
    * @param $context
    *   The batch context.
    */
-  public static function processBatch(ConfigImporter $config_importer, $sync_step, &$context) {
+  public static function processBatch(BatchConfigImporter $config_importer, $operation, &$context) {
     if (!isset($context['sandbox']['config_importer'])) {
       $context['sandbox']['config_importer'] = $config_importer;
     }
 
     $config_importer = $context['sandbox']['config_importer'];
-    $config_importer->doSyncStep($sync_step, $context);
+    $config_importer->$operation($context);
     if ($errors = $config_importer->getErrors()) {
       if (!isset($context['results']['errors'])) {
         $context['results']['errors'] = array();
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php
index 37875b80f12a2e4b16c6305ca8490b06434843e4..759765949fbfc63fc49da29ddc428ab254b4fca4 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php
@@ -30,7 +30,7 @@ class ConfigImporterTest extends DrupalUnitTestBase {
    *
    * @var array
    */
-  public static $modules = array('config_test', 'system', 'config_import_test');
+  public static $modules = array('config_test', 'system');
 
   public static function getInfo() {
     return array(
@@ -198,10 +198,6 @@ function testNew() {
     $this->assertFalse(isset($GLOBALS['hook_config_test']['predelete']));
     $this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
 
-    // Verify that hook_config_import_steps_alter() can add steps to
-    // configuration synchronization.
-    $this->assertTrue(isset($GLOBALS['hook_config_test']['config_import_steps_alter']));
-
     // Verify that there is nothing more to import.
     $this->assertFalse($this->configImporter->hasUnprocessedConfigurationChanges());
     $logs = $this->configImporter->getErrors();
diff --git a/core/modules/config/tests/config_import_test/config_import_test.module b/core/modules/config/tests/config_import_test/config_import_test.module
index 6ec1d93d81600c55a8e7fad9cb52943a02c19d0a..936b72b74b13fff69ba05e8407b319f3a7d16369 100644
--- a/core/modules/config/tests/config_import_test/config_import_test.module
+++ b/core/modules/config/tests/config_import_test/config_import_test.module
@@ -4,22 +4,3 @@
  * @file
  * Provides configuration import test helpers.
  */
-
-/**
- * Implements hook_config_import_steps_alter().
- */
-function config_import_test_config_import_steps_alter(&$sync_steps) {
-  $sync_steps[] = '_config_import_test_config_import_steps_alter';
-}
-
-/**
- * Implements configuration synchronization step added by an alter for testing.
- *
- * @param array $context
- *   The batch context.
- */
-function _config_import_test_config_import_steps_alter(&$context) {
-  $GLOBALS['hook_config_test']['config_import_steps_alter'] = TRUE;
-  $context['finished'] = 1;
-  return;
-}
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 146fe2f4c4912b0991c6b690496610ac13bf10de..550cd2888f778a2eb972e20f5f0f8d934e9015dd 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -2878,31 +2878,6 @@ function hook_link_alter(&$variables) {
   }
 }
 
-/**
- * Alter the configuration synchronization steps.
- *
- * @param array $sync_steps
- *   A one-dimensional array of \Drupal\Core\Config\ConfigImporter method names
- *   or callables that are invoked to complete the import, in the order that
- *   they will be processed. Each callable item defined in $sync_steps should
- *   either be a global function or a public static method. The callable should
- *   accept a $context array by reference. For example:
- *   <code>
- *     function _additional_configuration_step(&$context) {
- *       // Do stuff.
- *       // If finished set $context['finished'] = 1.
- *     }
- *   </code>
- *   For more information on creating batches, see the
- *   @link batch Batch operations @endlink documentation.
- *
- * @see callback_batch_operation()
- * @see \Drupal\Core\Config\ConfigImporter::initialize()
- */
-function hook_config_import_steps_alter(&$sync_steps) {
-  $sync_steps[] = '_additional_configuration_step';
-}
-
 /**
  * @} End of "addtogroup hooks".
  */