diff --git a/include/nlohmann/detail/iterators/iter_impl.hpp b/include/nlohmann/detail/iterators/iter_impl.hpp
index 52ede17b..08108a22 100644
--- a/include/nlohmann/detail/iterators/iter_impl.hpp
+++ b/include/nlohmann/detail/iterators/iter_impl.hpp
@@ -583,7 +583,7 @@ class iter_impl
     @brief  return the key of an object iterator
     @pre The iterator is initialized; i.e. `m_object != nullptr`.
     */
-    typename object_t::key_type key() const
+    const typename object_t::key_type& key() const
     {
         assert(m_object != nullptr);
 
diff --git a/include/nlohmann/detail/iterators/iteration_proxy.hpp b/include/nlohmann/detail/iterators/iteration_proxy.hpp
index 7547d038..047e1855 100644
--- a/include/nlohmann/detail/iterators/iteration_proxy.hpp
+++ b/include/nlohmann/detail/iterators/iteration_proxy.hpp
@@ -21,6 +21,12 @@ template<typename IteratorType> class iteration_proxy
         IteratorType anchor;
         /// an index for arrays (used to create key names)
         std::size_t array_index = 0;
+        /// last stringified array index
+        mutable std::size_t array_index_last = 0;
+        /// a string representation of the array index
+        mutable std::string array_index_str = "0";
+        /// an empty string (to return a reference for primitive values)
+        const std::string empty_str = "";
 
       public:
         explicit iteration_proxy_internal(IteratorType it) noexcept : anchor(it) {}
@@ -47,7 +53,7 @@ template<typename IteratorType> class iteration_proxy
         }
 
         /// return key of the iterator
-        std::string key() const
+        const std::string& key() const
         {
             assert(anchor.m_object != nullptr);
 
@@ -55,7 +61,14 @@ template<typename IteratorType> class iteration_proxy
             {
                 // use integer array index as key
                 case value_t::array:
-                    return std::to_string(array_index);
+                {
+                    if (array_index != array_index_last)
+                    {
+                        array_index_str = std::to_string(array_index);
+                        array_index_last = array_index;
+                    }
+                    return array_index_str;
+                }
 
                 // use key from the object
                 case value_t::object:
@@ -63,7 +76,7 @@ template<typename IteratorType> class iteration_proxy
 
                 // use an empty key for all primitive types
                 default:
-                    return "";
+                    return empty_str;
             }
         }
 
diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp
index 6559d23d..c880f457 100644
--- a/single_include/nlohmann/json.hpp
+++ b/single_include/nlohmann/json.hpp
@@ -5210,7 +5210,7 @@ class iter_impl
     @brief  return the key of an object iterator
     @pre The iterator is initialized; i.e. `m_object != nullptr`.
     */
-    typename object_t::key_type key() const
+    const typename object_t::key_type& key() const
     {
         assert(m_object != nullptr);
 
@@ -5265,6 +5265,12 @@ template<typename IteratorType> class iteration_proxy
         IteratorType anchor;
         /// an index for arrays (used to create key names)
         std::size_t array_index = 0;
+        /// last stringified array index
+        mutable std::size_t array_index_last = 0;
+        /// a string representation of the array index
+        mutable std::string array_index_str = "0";
+        /// an empty string (to return a reference for primitive values)
+        const std::string empty_str = "";
 
       public:
         explicit iteration_proxy_internal(IteratorType it) noexcept : anchor(it) {}
@@ -5291,7 +5297,7 @@ template<typename IteratorType> class iteration_proxy
         }
 
         /// return key of the iterator
-        std::string key() const
+        const std::string& key() const
         {
             assert(anchor.m_object != nullptr);
 
@@ -5299,7 +5305,14 @@ template<typename IteratorType> class iteration_proxy
             {
                 // use integer array index as key
                 case value_t::array:
-                    return std::to_string(array_index);
+                {
+                    if (array_index != array_index_last)
+                    {
+                        array_index_str = std::to_string(array_index);
+                        array_index_last = array_index;
+                    }
+                    return array_index_str;
+                }
 
                 // use key from the object
                 case value_t::object:
@@ -5307,7 +5320,7 @@ template<typename IteratorType> class iteration_proxy
 
                 // use an empty key for all primitive types
                 default:
-                    return "";
+                    return empty_str;
             }
         }