diff --git a/core/includes/common.inc b/core/includes/common.inc
index 3ff8096..887e615 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -4684,26 +4684,35 @@ function drupal_render(&$elements) {
   // Call the element's #theme function if it is set. Then any children of the
   // element have to be rendered there. If the internal #render_children
   // property is set, do not call the #theme function to prevent infinite
-  // recursion.
-  if (isset($elements['#theme']) && !isset($elements['#render_children'])) {
+  // recursion. Assume that if #theme is set it represents an implemented hook.
+  $theme_is_implemented = isset($elements['#theme']);
+  if ($theme_is_implemented && !isset($elements['#render_children'])) {
     $elements['#children'] = theme($elements['#theme'], $elements);
+
+    // If theme() returns FALSE this means that the hook in #theme was not found
+    // in the registry and so we need to update our flag accordingly. This is
+    // common for theme suggestions.
+    $theme_is_implemented = ($elements['#children'] !== FALSE);
   }
-  // If #theme was not set and the element has children, render them now.
-  // This is the same process as drupal_render_children() but is inlined
-  // for speed.
-  if ($elements['#children'] === '') {
+  // #theme is either not set or does not exist in the registry.
+  if (!$theme_is_implemented) {
+    // If #theme is not implemented and the element has children, render them
+    // now. This is the same process as drupal_render_children() but is inlined
+    // for speed.
     foreach ($children as $key) {
       $elements['#children'] .= drupal_render($elements[$key]);
     }
-  }
-  // If #theme was not set, but the element has raw #markup, prepend the content
-  // in #markup to #children. #children may contain the rendered content
-  // supplied by #theme, or the rendered child elements, as processed above. If
-  // both #theme and #markup are set, then #theme is responsible for rendering
-  // the element. Eventually assigned #theme_wrappers will expect both the
-  // element's #markup and the rendered content of child elements in #children.
-  if (!isset($elements['#theme']) && isset($elements['#markup'])) {
-    $elements['#children'] = $elements['#markup'] . $elements['#children'];
+
+    // If #theme is not implemented and the element has raw #markup as a
+    // fallback, prepend the content in #markup to #children. In this case
+    // #children will contain whatever is provided by #pre_render prepended to
+    // what is rendered recursively above. If #theme is implemented then it is
+    // the responsibility of that theme implementation to render #markup if
+    // required. Eventually #theme_wrappers will expect both #markup and
+    // #children to be a single string as #children.
+    if (isset($elements['#markup'])) {
+      $elements['#children'] = $elements['#markup'] . $elements['#children'];
+    }
   }
 
   // Add any JavaScript state information associated with the element.
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index a7da8d0..3b60eff 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -932,8 +932,9 @@ function drupal_find_base_themes($themes, $key, $used_keys = array()) {
  *   properties are mapped to variables expected by the theme hook
  *   implementations.
  *
- * @return
- *   An HTML string representing the themed output.
+ * @return string|false
+ *   An HTML string representing the themed output or FALSE if the passed $hook
+ *   is not implemented.
  *
  * @see themeable
  * @see hook_theme()
@@ -982,7 +983,10 @@ function theme($hook, $variables = array()) {
       if (!isset($candidate)) {
         watchdog('theme', 'Theme hook %hook not found.', array('%hook' => $hook), WATCHDOG_WARNING);
       }
-      return '';
+      // There is no theme implementation for the hook passed. Return FALSE so
+      // the function calling theme() can differentiate between a hook that
+      // exists and renders an empty string and a hook that is not implemented.
+      return FALSE;
     }
   }
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php
index 31e4f34..8365f3f 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php
@@ -77,6 +77,18 @@ function testDrupalRenderBasics() {
         'value' => array('#markup' => 'foo'),
         'expected' => 'foo',
       ),
+      array(
+        'name' => '#render_children is set,  #theme is implemented, array has children',
+        'value' => array(
+          '#theme' => 'common_test_foo',
+          '#children' => 'baz',
+          '#render_children' => TRUE,
+          'child' => array(
+            '#markup' => 'foo',
+          ),
+        ),
+        'expected' => 'baz',
+      ),
     );
     foreach($types as $type) {
       $this->assertIdentical(drupal_render($type['value']), $type['expected'], '"' . $type['name'] . '" input rendered correctly by drupal_render().');
@@ -84,6 +96,59 @@ function testDrupalRenderBasics() {
   }
 
   /**
+   * Tests fallback rendering behaviour when #theme is not implemented.
+   *
+   * If #theme is set and is an implemented theme hook then theme() is 100%
+   * responsible for rendering the array including children and #markup.
+   *
+   * If #theme is not set or is not found in the registry then drupal_render()
+   * should recursively render child attributes of the array and #markup.
+   *
+   * This dual rendering behaviour is only relevant to the internal processing
+   * of drupal_render() before #theme_wrappers are called, so not #prefix and
+   * #suffix for example.
+   */
+  function testDrupalRenderFallbackRender() {
+    // Theme suggestion is not implemented, #markup should be rendered.
+    $theme_suggestion_not_implemented_has_markup = array(
+      '#theme' => array('suggestionnotimplemented'),
+      '#markup' => 'foo',
+    );
+    $rendered = drupal_render($theme_suggestion_not_implemented_has_markup);
+    $this->assertIdentical($rendered, 'foo');
+
+    // Theme suggestion is not implemented, children should be rendered.
+    $theme_suggestion_not_implemented_has_children = array(
+      '#theme' => array('suggestionnotimplemented'),
+      'child' => array(
+        '#markup' => 'foo',
+      ),
+    );
+    $rendered = drupal_render($theme_suggestion_not_implemented_has_children);
+    $this->assertIdentical($rendered, 'foo');
+
+    // Theme suggestion is implemented but returns empty string, #markup should
+    // not be rendered.
+    $theme_implemented_is_empty_has_markup = array(
+      '#theme' => array('common_test_empty'),
+      '#markup' => 'foo',
+    );
+    $rendered = drupal_render($theme_implemented_is_empty_has_markup);
+    $this->assertIdentical($rendered, '');
+
+    // Theme suggestion is implemented but returns empty string, children should
+    // not be rendered.
+    $theme_implemented_is_empty_has_children = array(
+      '#theme' => array('common_test_empty'),
+      'child' => array(
+        '#markup' => 'foo',
+      ),
+    );
+    $rendered = drupal_render($theme_implemented_is_empty_has_children);
+    $this->assertIdentical($rendered, '');
+  }
+
+  /**
    * Tests sorting by weight.
    */
   function testDrupalRenderSorting() {
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php
index 32f9321..072bfb0 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php
@@ -204,7 +204,7 @@ function testRegistryRebuild() {
     // throws an exception.
     $this->rebuildContainer();
     $this->container->get('module_handler')->loadAll();
-    $this->assertIdentical(theme('theme_test_foo', array('foo' => 'b')), '', 'The theme registry does not contain theme_test_foo, because the module is disabled.');
+    $this->assertIdentical(theme('theme_test_foo', array('foo' => 'b')), FALSE, 'The theme registry does not contain theme_test_foo, because the module is disabled.');
 
     module_enable(array('theme_test'), FALSE);
     // After enabling/disabling a module during a test, we need to rebuild the
diff --git a/core/modules/system/tests/modules/common_test/common_test.module b/core/modules/system/tests/modules/common_test/common_test.module
index 2c11a8b..bd44401 100644
--- a/core/modules/system/tests/modules/common_test/common_test.module
+++ b/core/modules/system/tests/modules/common_test/common_test.module
@@ -164,10 +164,27 @@ function common_test_theme() {
       'variables' => array('foo' => 'foo', 'bar' => 'bar'),
       'template' => 'common-test-foo',
     ),
+    'common_test_empty' => array(
+      'variables' => array('foo' => 'foo'),
+    ),
   );
 }
 
 /**
+ * Provides a theme function for drupal_render().
+ */
+function theme_common_test_foo($variables) {
+  return $variables['foo'] . $variables['bar'];
+}
+
+/**
+ * Always returns an empty string.
+ */
+function theme_common_test_empty($variables) {
+  return '';
+}
+
+/**
  * Implements hook_library_info_alter().
  */
 function common_test_library_info_alter(&$libraries, $module) {
