diff --git a/header_only/json.h b/header_only/json.h
index ba992d64..ed932b9d 100644
--- a/header_only/json.h
+++ b/header_only/json.h
@@ -42,11 +42,12 @@ due to alignment.
 */
 class json
 {
-  public:
+  private:
     // forward declaration to friend this class
     class iterator;
     class const_iterator;
 
+  public:
     // container types
     using value_type = json;
     using reference = json&;
@@ -371,15 +372,15 @@ class json
     /// the payload
     value value_ {};
 
-  public:
+  private:
     /// an iterator
-    class iterator : public std::iterator<std::forward_iterator_tag, json>
+    class iterator : private std::iterator<std::forward_iterator_tag, json>
     {
         friend class json;
         friend class json::const_iterator;
       public:
         iterator() = default;
-        iterator(json*);
+        iterator(json*, bool);
         iterator(const iterator&);
         ~iterator();
 
@@ -402,16 +403,18 @@ class json
         array_t::iterator* vi_ = nullptr;
         /// an iterator for JSON objects
         object_t::iterator* oi_ = nullptr;
+        /// whether iterator points to a valid object
+        bool invalid = true;
     };
 
     /// a const iterator
-    class const_iterator : public std::iterator<std::forward_iterator_tag, const json>
+    class const_iterator : private std::iterator<std::forward_iterator_tag, const json>
     {
         friend class json;
 
       public:
         const_iterator() = default;
-        const_iterator(const json*);
+        const_iterator(const json*, bool);
         const_iterator(const const_iterator&);
         const_iterator(const json::iterator&);
         ~const_iterator();
@@ -435,6 +438,8 @@ class json
         array_t::const_iterator* vi_ = nullptr;
         /// an iterator for JSON objects
         object_t::const_iterator* oi_ = nullptr;
+        /// whether iterator reached past the end
+        bool invalid = true;
     };
 
   private:
@@ -1769,50 +1774,28 @@ json::const_iterator json::find(const std::string& key) const
 
 json::iterator json::find(const char* key)
 {
-    if (type_ != value_t::object)
+    auto result = end();
+
+    if (type_ == value_t::object)
     {
-        return end();
-    }
-    else
-    {
-        const object_t::iterator i = value_.object->find(key);
-        if (i != value_.object->end())
-        {
-            json::iterator result(this);
-            delete result.oi_;
-            result.oi_ = nullptr;
-            result.oi_ = new object_t::iterator(i);
-            return result;
-        }
-        else
-        {
-            return end();
-        }
+        result.oi_ = new object_t::iterator(value_.object->find(key));
+        result.invalid = (*(result.oi_) == value_.object->end());
     }
+
+    return result;
 }
 
 json::const_iterator json::find(const char* key) const
 {
-    if (type_ != value_t::object)
+    auto result = cend();
+
+    if (type_ == value_t::object)
     {
-        return end();
-    }
-    else
-    {
-        const object_t::const_iterator i = value_.object->find(key);
-        if (i != value_.object->end())
-        {
-            json::const_iterator result(this);
-            delete result.oi_;
-            result.oi_ = nullptr;
-            result.oi_ = new object_t::const_iterator(i);
-            return result;
-        }
-        else
-        {
-            return end();
-        }
+        result.oi_ = new object_t::const_iterator(value_.object->find(key));
+        result.invalid = (*(result.oi_) == value_.object->cend());
     }
+
+    return result;
 }
 
 bool json::operator==(const json& o) const noexcept
@@ -1896,90 +1879,78 @@ bool json::operator!=(const json& o) const noexcept
 
 json::iterator json::begin() noexcept
 {
-    return json::iterator(this);
+    return json::iterator(this, true);
 }
 
 json::iterator json::end() noexcept
 {
-    return json::iterator();
+    return json::iterator(this, false);
 }
 
 json::const_iterator json::begin() const noexcept
 {
-    return json::const_iterator(this);
+    return json::const_iterator(this, true);
 }
 
 json::const_iterator json::end() const noexcept
 {
-    return json::const_iterator();
+    return json::const_iterator(this, false);
 }
 
 json::const_iterator json::cbegin() const noexcept
 {
-    return json::const_iterator(this);
+    return json::const_iterator(this, true);
 }
 
 json::const_iterator json::cend() const noexcept
 {
-    return json::const_iterator();
+    return json::const_iterator(this, false);
 }
 
 
-json::iterator::iterator(json* j) : object_(j)
+json::iterator::iterator(json* j, bool begin)
+    : object_(j), invalid(not begin or j == nullptr)
 {
     if (object_ != nullptr)
     {
         if (object_->type_ == json::value_t::array)
         {
-            if (object_->empty())
+            if (begin)
             {
-                object_ = nullptr;
+                vi_ = new array_t::iterator(object_->value_.array->begin());
+                invalid = (*vi_ == object_->value_.array->end());
             }
             else
             {
-                vi_ = new array_t::iterator(object_->value_.array->begin());
+                vi_ = new array_t::iterator(object_->value_.array->end());
             }
         }
         else if (object_->type_ == json::value_t::object)
         {
-            if (object_->empty())
+            if (begin)
             {
-                object_ = nullptr;
+                oi_ = new object_t::iterator(object_->value_.object->begin());
+                invalid = (*oi_ == object_->value_.object->end());
             }
             else
             {
-                oi_ = new object_t::iterator(object_->value_.object->begin());
+                oi_ = new object_t::iterator(object_->value_.object->end());
             }
         }
     }
 }
 
-json::iterator::iterator(const json::iterator& o) : object_(o.object_)
+json::iterator::iterator(const json::iterator& o)
+    : object_(o.object_), invalid(o.invalid)
 {
-    if (object_ != nullptr)
+    if (o.vi_ != nullptr)
     {
-        if (object_->type_ == json::value_t::array)
-        {
-            if (object_->empty())
-            {
-                object_ = nullptr;
-            }
-            else
-            {
-                vi_ = new array_t::iterator(object_->value_.array->begin());
-            }
-        }
-        else if (object_->type_ == json::value_t::object)
-        {
-            if (object_->empty())
-            {
-                object_ = nullptr;
-            }
-            else
-            {
-                oi_ = new object_t::iterator(object_->value_.object->begin());
-            }
-        }
+        vi_ = new array_t::iterator(*(o.vi_));
+    }
+
+    if (o.oi_ != nullptr)
+    {
+        oi_ = new object_t::iterator(*(o.oi_));
     }
 }
 
@@ -1994,29 +1965,29 @@ json::iterator& json::iterator::operator=(json::iterator o)
     std::swap(object_, o.object_);
     std::swap(vi_, o.vi_);
     std::swap(oi_, o.oi_);
+    std::swap(invalid, o.invalid);
     return *this;
 }
 
 bool json::iterator::operator==(const json::iterator& o) const
 {
-    if (object_ != o.object_)
+    if (object_ != nullptr and o.object_ != nullptr)
     {
-        return false;
-    }
-
-    if (object_ != nullptr)
-    {
-        if (object_->type_ == json::value_t::array)
+        if (object_->type_ == json::value_t::array and o.object_->type_ == json::value_t::array)
         {
-            return (vi_ == o.vi_);
+            return (*vi_ == *(o.vi_));
         }
-        if (object_->type_ == json::value_t::object)
+        if (object_->type_ == json::value_t::object and o.object_->type_ == json::value_t::object)
         {
-            return (oi_ == o.oi_);
+            return (*oi_ == *(o.oi_));
+        }
+
+        if (invalid == o.invalid and object_ == o.object_)
+        {
+            return true;
         }
     }
-
-    return true;
+    return false;
 }
 
 bool json::iterator::operator!=(const json::iterator& o) const
@@ -2026,44 +1997,38 @@ bool json::iterator::operator!=(const json::iterator& o) const
 
 json::iterator& json::iterator::operator++()
 {
-    // iterator cannot be incremented
-    if (object_ == nullptr)
+    if (object_ != nullptr)
     {
-        return *this;
+        switch (object_->type_)
+        {
+            case (json::value_t::array):
+            {
+                std::advance(*vi_, 1);
+                invalid = (*vi_ == object_->value_.array->end());
+                break;
+            }
+            case (json::value_t::object):
+            {
+                std::advance(*oi_, 1);
+                invalid = (*oi_ == object_->value_.object->end());
+                break;
+            }
+            default:
+            {
+                invalid = true;
+                break;
+            }
+        }
     }
 
-    switch (object_->type_)
-    {
-        case (json::value_t::array):
-        {
-            if (++(*vi_) == object_->value_.array->end())
-            {
-                object_ = nullptr;
-            }
-            break;
-        }
-        case (json::value_t::object):
-        {
-            if (++(*oi_) == object_->value_.object->end())
-            {
-                object_ = nullptr;
-            }
-            break;
-        }
-        default:
-        {
-            object_ = nullptr;
-        }
-    }
     return *this;
 }
 
 json& json::iterator::operator*() const
 {
-    // dereferencing end() is an error
-    if (object_ == nullptr)
+    if (object_ == nullptr or invalid)
     {
-        throw std::runtime_error("cannot get value");
+        throw std::out_of_range("cannot get value");
     }
 
     switch (object_->type_)
@@ -2085,10 +2050,9 @@ json& json::iterator::operator*() const
 
 json* json::iterator::operator->() const
 {
-    // dereferencing end() is an error
-    if (object_ == nullptr)
+    if (object_ == nullptr or invalid)
     {
-        throw std::runtime_error("cannot get value");
+        throw std::out_of_range("cannot get value");
     }
 
     switch (object_->type_)
@@ -2110,20 +2074,17 @@ json* json::iterator::operator->() const
 
 std::string json::iterator::key() const
 {
-    if (object_ != nullptr and object_->type_ == json::value_t::object)
+    if (object_ == nullptr or invalid or object_->type_ != json::value_t::object)
     {
-        return (*oi_)->first;
-    }
-    else
-    {
-        throw std::out_of_range("cannot get key");
+        throw std::out_of_range("cannot get value");
     }
+
+    return (*oi_)->first;
 }
 
 json& json::iterator::value() const
 {
-    // dereferencing end() is an error
-    if (object_ == nullptr)
+    if (object_ == nullptr or invalid)
     {
         throw std::out_of_range("cannot get value");
     }
@@ -2146,90 +2107,61 @@ json& json::iterator::value() const
 }
 
 
-json::const_iterator::const_iterator(const json* j) : object_(j)
+json::const_iterator::const_iterator(const json* j, bool begin)
+    : object_(j), invalid(not begin or j == nullptr)
 {
     if (object_ != nullptr)
     {
         if (object_->type_ == json::value_t::array)
         {
-            if (object_->empty())
+            if (begin)
             {
-                object_ = nullptr;
+                vi_ = new array_t::const_iterator(object_->value_.array->cbegin());
+                invalid = (*vi_ == object_->value_.array->cend());
             }
             else
             {
-                vi_ = new array_t::const_iterator(object_->value_.array->begin());
+                vi_ = new array_t::const_iterator(object_->value_.array->cend());
             }
         }
         else if (object_->type_ == json::value_t::object)
         {
-            if (object_->empty())
+            if (begin)
             {
-                object_ = nullptr;
+                oi_ = new object_t::const_iterator(object_->value_.object->cbegin());
+                invalid = (*oi_ == object_->value_.object->cend());
             }
             else
             {
-                oi_ = new object_t::const_iterator(object_->value_.object->begin());
+                oi_ = new object_t::const_iterator(object_->value_.object->cend());
             }
         }
     }
 }
 
-json::const_iterator::const_iterator(const json::const_iterator& o) : object_(o.object_)
+json::const_iterator::const_iterator(const json::const_iterator& o)
+    : object_(o.object_), invalid(o.invalid)
 {
-    if (object_ != nullptr)
+    if (o.vi_ != nullptr)
     {
-        if (object_->type_ == json::value_t::array)
-        {
-            if (object_->empty())
-            {
-                object_ = nullptr;
-            }
-            else
-            {
-                vi_ = new array_t::const_iterator(object_->value_.array->begin());
-            }
-        }
-        else if (object_->type_ == json::value_t::object)
-        {
-            if (object_->empty())
-            {
-                object_ = nullptr;
-            }
-            else
-            {
-                oi_ = new object_t::const_iterator(object_->value_.object->begin());
-            }
-        }
+        vi_ = new array_t::const_iterator(*(o.vi_));
+    }
+    if (o.oi_ != nullptr)
+    {
+        oi_ = new object_t::const_iterator(*(o.oi_));
     }
 }
 
-json::const_iterator::const_iterator(const json::iterator& o) : object_(o.object_)
+json::const_iterator::const_iterator(const json::iterator& o)
+    : object_(o.object_), invalid(o.invalid)
 {
-    if (object_ != nullptr)
+    if (o.vi_ != nullptr)
     {
-        if (object_->type_ == json::value_t::array)
-        {
-            if (object_->empty())
-            {
-                object_ = nullptr;
-            }
-            else
-            {
-                vi_ = new array_t::const_iterator(object_->value_.array->begin());
-            }
-        }
-        else if (object_->type_ == json::value_t::object)
-        {
-            if (object_->empty())
-            {
-                object_ = nullptr;
-            }
-            else
-            {
-                oi_ = new object_t::const_iterator(object_->value_.object->begin());
-            }
-        }
+        vi_ = new array_t::const_iterator(*(o.vi_));
+    }
+    if (o.oi_ != nullptr)
+    {
+        oi_ = new object_t::const_iterator(*(o.oi_));
     }
 }
 
@@ -2244,29 +2176,29 @@ json::const_iterator& json::const_iterator::operator=(json::const_iterator o)
     std::swap(object_, o.object_);
     std::swap(vi_, o.vi_);
     std::swap(oi_, o.oi_);
+    std::swap(invalid, o.invalid);
     return *this;
 }
 
 bool json::const_iterator::operator==(const json::const_iterator& o) const
 {
-    if (object_ != o.object_)
+    if (object_ != nullptr and o.object_ != nullptr)
     {
-        return false;
-    }
-
-    if (object_ != nullptr)
-    {
-        if (object_->type_ == json::value_t::array)
+        if (object_->type_ == json::value_t::array and o.object_->type_ == json::value_t::array)
         {
-            return (vi_ == o.vi_);
+            return (*vi_ == *(o.vi_));
         }
-        if (object_->type_ == json::value_t::object)
+        if (object_->type_ == json::value_t::object and o.object_->type_ == json::value_t::object)
         {
-            return (oi_ == o.oi_);
+            return (*oi_ == *(o.oi_));
+        }
+        if (invalid == o.invalid and object_ == o.object_)
+        {
+            return true;
         }
     }
 
-    return true;
+    return false;
 }
 
 bool json::const_iterator::operator!=(const json::const_iterator& o) const
@@ -2276,44 +2208,38 @@ bool json::const_iterator::operator!=(const json::const_iterator& o) const
 
 json::const_iterator& json::const_iterator::operator++()
 {
-    // iterator cannot be incremented
-    if (object_ == nullptr)
+    if (object_ != nullptr)
     {
-        return *this;
+        switch (object_->type_)
+        {
+            case (json::value_t::array):
+            {
+                std::advance(*vi_, 1);
+                invalid = (*vi_ == object_->value_.array->end());
+                break;
+            }
+            case (json::value_t::object):
+            {
+                std::advance(*oi_, 1);
+                invalid = (*oi_ == object_->value_.object->end());
+                break;
+            }
+            default:
+            {
+                invalid = true;
+                break;
+            }
+        }
     }
 
-    switch (object_->type_)
-    {
-        case (json::value_t::array):
-        {
-            if (++(*vi_) == object_->value_.array->end())
-            {
-                object_ = nullptr;
-            }
-            break;
-        }
-        case (json::value_t::object):
-        {
-            if (++(*oi_) == object_->value_.object->end())
-            {
-                object_ = nullptr;
-            }
-            break;
-        }
-        default:
-        {
-            object_ = nullptr;
-        }
-    }
     return *this;
 }
 
 const json& json::const_iterator::operator*() const
 {
-    // dereferencing end() is an error
-    if (object_ == nullptr)
+    if (object_ == nullptr or invalid)
     {
-        throw std::runtime_error("cannot get value");
+        throw std::out_of_range("cannot get value");
     }
 
     switch (object_->type_)
@@ -2335,10 +2261,9 @@ const json& json::const_iterator::operator*() const
 
 const json* json::const_iterator::operator->() const
 {
-    // dereferencing end() is an error
-    if (object_ == nullptr)
+    if (object_ == nullptr or invalid)
     {
-        throw std::runtime_error("cannot get value");
+        throw std::out_of_range("cannot get value");
     }
 
     switch (object_->type_)
@@ -2360,20 +2285,17 @@ const json* json::const_iterator::operator->() const
 
 std::string json::const_iterator::key() const
 {
-    if (object_ != nullptr and object_->type_ == json::value_t::object)
+    if (object_ == nullptr or invalid or object_->type_ != json::value_t::object)
     {
-        return (*oi_)->first;
-    }
-    else
-    {
-        throw std::out_of_range("cannot get key");
+        throw std::out_of_range("cannot get value");
     }
+
+    return (*oi_)->first;
 }
 
 const json& json::const_iterator::value() const
 {
-    // dereferencing end() is an error
-    if (object_ == nullptr)
+    if (object_ == nullptr or invalid)
     {
         throw std::out_of_range("cannot get value");
     }
diff --git a/src/json.cc b/src/json.cc
index 69c0e0cf..c6e13f80 100644
--- a/src/json.cc
+++ b/src/json.cc
@@ -1245,50 +1245,28 @@ json::const_iterator json::find(const std::string& key) const
 
 json::iterator json::find(const char* key)
 {
-    if (type_ != value_t::object)
+    auto result = end();
+
+    if (type_ == value_t::object)
     {
-        return end();
-    }
-    else
-    {
-        const object_t::iterator i = value_.object->find(key);
-        if (i != value_.object->end())
-        {
-            json::iterator result(this);
-            delete result.oi_;
-            result.oi_ = nullptr;
-            result.oi_ = new object_t::iterator(i);
-            return result;
-        }
-        else
-        {
-            return end();
-        }
+        result.oi_ = new object_t::iterator(value_.object->find(key));
+        result.invalid = (*(result.oi_) == value_.object->end());
     }
+
+    return result;
 }
 
 json::const_iterator json::find(const char* key) const
 {
-    if (type_ != value_t::object)
+    auto result = cend();
+
+    if (type_ == value_t::object)
     {
-        return end();
-    }
-    else
-    {
-        const object_t::const_iterator i = value_.object->find(key);
-        if (i != value_.object->end())
-        {
-            json::const_iterator result(this);
-            delete result.oi_;
-            result.oi_ = nullptr;
-            result.oi_ = new object_t::const_iterator(i);
-            return result;
-        }
-        else
-        {
-            return end();
-        }
+        result.oi_ = new object_t::const_iterator(value_.object->find(key));
+        result.invalid = (*(result.oi_) == value_.object->cend());
     }
+
+    return result;
 }
 
 bool json::operator==(const json& o) const noexcept
@@ -1372,90 +1350,78 @@ bool json::operator!=(const json& o) const noexcept
 
 json::iterator json::begin() noexcept
 {
-    return json::iterator(this);
+    return json::iterator(this, true);
 }
 
 json::iterator json::end() noexcept
 {
-    return json::iterator();
+    return json::iterator(this, false);
 }
 
 json::const_iterator json::begin() const noexcept
 {
-    return json::const_iterator(this);
+    return json::const_iterator(this, true);
 }
 
 json::const_iterator json::end() const noexcept
 {
-    return json::const_iterator();
+    return json::const_iterator(this, false);
 }
 
 json::const_iterator json::cbegin() const noexcept
 {
-    return json::const_iterator(this);
+    return json::const_iterator(this, true);
 }
 
 json::const_iterator json::cend() const noexcept
 {
-    return json::const_iterator();
+    return json::const_iterator(this, false);
 }
 
 
-json::iterator::iterator(json* j) : object_(j)
+json::iterator::iterator(json* j, bool begin)
+    : object_(j), invalid(not begin or j == nullptr)
 {
     if (object_ != nullptr)
     {
         if (object_->type_ == json::value_t::array)
         {
-            if (object_->empty())
+            if (begin)
             {
-                object_ = nullptr;
+                vi_ = new array_t::iterator(object_->value_.array->begin());
+                invalid = (*vi_ == object_->value_.array->end());
             }
             else
             {
-                vi_ = new array_t::iterator(object_->value_.array->begin());
+                vi_ = new array_t::iterator(object_->value_.array->end());
             }
         }
         else if (object_->type_ == json::value_t::object)
         {
-            if (object_->empty())
+            if (begin)
             {
-                object_ = nullptr;
+                oi_ = new object_t::iterator(object_->value_.object->begin());
+                invalid = (*oi_ == object_->value_.object->end());
             }
             else
             {
-                oi_ = new object_t::iterator(object_->value_.object->begin());
+                oi_ = new object_t::iterator(object_->value_.object->end());
             }
         }
     }
 }
 
-json::iterator::iterator(const json::iterator& o) : object_(o.object_)
+json::iterator::iterator(const json::iterator& o)
+    : object_(o.object_), invalid(o.invalid)
 {
-    if (object_ != nullptr)
+    if (o.vi_ != nullptr)
     {
-        if (object_->type_ == json::value_t::array)
-        {
-            if (object_->empty())
-            {
-                object_ = nullptr;
-            }
-            else
-            {
-                vi_ = new array_t::iterator(object_->value_.array->begin());
-            }
-        }
-        else if (object_->type_ == json::value_t::object)
-        {
-            if (object_->empty())
-            {
-                object_ = nullptr;
-            }
-            else
-            {
-                oi_ = new object_t::iterator(object_->value_.object->begin());
-            }
-        }
+        vi_ = new array_t::iterator(*(o.vi_));
+    }
+
+    if (o.oi_ != nullptr)
+    {
+        oi_ = new object_t::iterator(*(o.oi_));
     }
 }
 
@@ -1470,29 +1436,29 @@ json::iterator& json::iterator::operator=(json::iterator o)
     std::swap(object_, o.object_);
     std::swap(vi_, o.vi_);
     std::swap(oi_, o.oi_);
+    std::swap(invalid, o.invalid);
     return *this;
 }
 
 bool json::iterator::operator==(const json::iterator& o) const
 {
-    if (object_ != o.object_)
+    if (object_ != nullptr and o.object_ != nullptr)
     {
-        return false;
-    }
-
-    if (object_ != nullptr)
-    {
-        if (object_->type_ == json::value_t::array)
+        if (object_->type_ == json::value_t::array and o.object_->type_ == json::value_t::array)
         {
-            return (vi_ == o.vi_);
+            return (*vi_ == *(o.vi_));
         }
-        if (object_->type_ == json::value_t::object)
+        if (object_->type_ == json::value_t::object and o.object_->type_ == json::value_t::object)
         {
-            return (oi_ == o.oi_);
+            return (*oi_ == *(o.oi_));
+        }
+
+        if (invalid == o.invalid and object_ == o.object_)
+        {
+            return true;
         }
     }
-
-    return true;
+    return false;
 }
 
 bool json::iterator::operator!=(const json::iterator& o) const
@@ -1502,44 +1468,38 @@ bool json::iterator::operator!=(const json::iterator& o) const
 
 json::iterator& json::iterator::operator++()
 {
-    // iterator cannot be incremented
-    if (object_ == nullptr)
+    if (object_ != nullptr)
     {
-        return *this;
+        switch (object_->type_)
+        {
+            case (json::value_t::array):
+            {
+                std::advance(*vi_, 1);
+                invalid = (*vi_ == object_->value_.array->end());
+                break;
+            }
+            case (json::value_t::object):
+            {
+                std::advance(*oi_, 1);
+                invalid = (*oi_ == object_->value_.object->end());
+                break;
+            }
+            default:
+            {
+                invalid = true;
+                break;
+            }
+        }
     }
 
-    switch (object_->type_)
-    {
-        case (json::value_t::array):
-        {
-            if (++(*vi_) == object_->value_.array->end())
-            {
-                object_ = nullptr;
-            }
-            break;
-        }
-        case (json::value_t::object):
-        {
-            if (++(*oi_) == object_->value_.object->end())
-            {
-                object_ = nullptr;
-            }
-            break;
-        }
-        default:
-        {
-            object_ = nullptr;
-        }
-    }
     return *this;
 }
 
 json& json::iterator::operator*() const
 {
-    // dereferencing end() is an error
-    if (object_ == nullptr)
+    if (object_ == nullptr or invalid)
     {
-        throw std::runtime_error("cannot get value");
+        throw std::out_of_range("cannot get value");
     }
 
     switch (object_->type_)
@@ -1561,10 +1521,9 @@ json& json::iterator::operator*() const
 
 json* json::iterator::operator->() const
 {
-    // dereferencing end() is an error
-    if (object_ == nullptr)
+    if (object_ == nullptr or invalid)
     {
-        throw std::runtime_error("cannot get value");
+        throw std::out_of_range("cannot get value");
     }
 
     switch (object_->type_)
@@ -1586,20 +1545,17 @@ json* json::iterator::operator->() const
 
 std::string json::iterator::key() const
 {
-    if (object_ != nullptr and object_->type_ == json::value_t::object)
+    if (object_ == nullptr or invalid or object_->type_ != json::value_t::object)
     {
-        return (*oi_)->first;
-    }
-    else
-    {
-        throw std::out_of_range("cannot get key");
+        throw std::out_of_range("cannot get value");
     }
+
+    return (*oi_)->first;
 }
 
 json& json::iterator::value() const
 {
-    // dereferencing end() is an error
-    if (object_ == nullptr)
+    if (object_ == nullptr or invalid)
     {
         throw std::out_of_range("cannot get value");
     }
@@ -1622,90 +1578,61 @@ json& json::iterator::value() const
 }
 
 
-json::const_iterator::const_iterator(const json* j) : object_(j)
+json::const_iterator::const_iterator(const json* j, bool begin)
+    : object_(j), invalid(not begin or j == nullptr)
 {
     if (object_ != nullptr)
     {
         if (object_->type_ == json::value_t::array)
         {
-            if (object_->empty())
+            if (begin)
             {
-                object_ = nullptr;
+                vi_ = new array_t::const_iterator(object_->value_.array->cbegin());
+                invalid = (*vi_ == object_->value_.array->cend());
             }
             else
             {
-                vi_ = new array_t::const_iterator(object_->value_.array->begin());
+                vi_ = new array_t::const_iterator(object_->value_.array->cend());
             }
         }
         else if (object_->type_ == json::value_t::object)
         {
-            if (object_->empty())
+            if (begin)
             {
-                object_ = nullptr;
+                oi_ = new object_t::const_iterator(object_->value_.object->cbegin());
+                invalid = (*oi_ == object_->value_.object->cend());
             }
             else
             {
-                oi_ = new object_t::const_iterator(object_->value_.object->begin());
+                oi_ = new object_t::const_iterator(object_->value_.object->cend());
             }
         }
     }
 }
 
-json::const_iterator::const_iterator(const json::const_iterator& o) : object_(o.object_)
+json::const_iterator::const_iterator(const json::const_iterator& o)
+    : object_(o.object_), invalid(o.invalid)
 {
-    if (object_ != nullptr)
+    if (o.vi_ != nullptr)
     {
-        if (object_->type_ == json::value_t::array)
-        {
-            if (object_->empty())
-            {
-                object_ = nullptr;
-            }
-            else
-            {
-                vi_ = new array_t::const_iterator(object_->value_.array->begin());
-            }
-        }
-        else if (object_->type_ == json::value_t::object)
-        {
-            if (object_->empty())
-            {
-                object_ = nullptr;
-            }
-            else
-            {
-                oi_ = new object_t::const_iterator(object_->value_.object->begin());
-            }
-        }
+        vi_ = new array_t::const_iterator(*(o.vi_));
+    }
+    if (o.oi_ != nullptr)
+    {
+        oi_ = new object_t::const_iterator(*(o.oi_));
     }
 }
 
-json::const_iterator::const_iterator(const json::iterator& o) : object_(o.object_)
+json::const_iterator::const_iterator(const json::iterator& o)
+    : object_(o.object_), invalid(o.invalid)
 {
-    if (object_ != nullptr)
+    if (o.vi_ != nullptr)
     {
-        if (object_->type_ == json::value_t::array)
-        {
-            if (object_->empty())
-            {
-                object_ = nullptr;
-            }
-            else
-            {
-                vi_ = new array_t::const_iterator(object_->value_.array->begin());
-            }
-        }
-        else if (object_->type_ == json::value_t::object)
-        {
-            if (object_->empty())
-            {
-                object_ = nullptr;
-            }
-            else
-            {
-                oi_ = new object_t::const_iterator(object_->value_.object->begin());
-            }
-        }
+        vi_ = new array_t::const_iterator(*(o.vi_));
+    }
+    if (o.oi_ != nullptr)
+    {
+        oi_ = new object_t::const_iterator(*(o.oi_));
     }
 }
 
@@ -1720,29 +1647,29 @@ json::const_iterator& json::const_iterator::operator=(json::const_iterator o)
     std::swap(object_, o.object_);
     std::swap(vi_, o.vi_);
     std::swap(oi_, o.oi_);
+    std::swap(invalid, o.invalid);
     return *this;
 }
 
 bool json::const_iterator::operator==(const json::const_iterator& o) const
 {
-    if (object_ != o.object_)
+    if (object_ != nullptr and o.object_ != nullptr)
     {
-        return false;
-    }
-
-    if (object_ != nullptr)
-    {
-        if (object_->type_ == json::value_t::array)
+        if (object_->type_ == json::value_t::array and o.object_->type_ == json::value_t::array)
         {
-            return (vi_ == o.vi_);
+            return (*vi_ == *(o.vi_));
         }
-        if (object_->type_ == json::value_t::object)
+        if (object_->type_ == json::value_t::object and o.object_->type_ == json::value_t::object)
         {
-            return (oi_ == o.oi_);
+            return (*oi_ == *(o.oi_));
+        }
+        if (invalid == o.invalid and object_ == o.object_)
+        {
+            return true;
         }
     }
 
-    return true;
+    return false;
 }
 
 bool json::const_iterator::operator!=(const json::const_iterator& o) const
@@ -1752,44 +1679,38 @@ bool json::const_iterator::operator!=(const json::const_iterator& o) const
 
 json::const_iterator& json::const_iterator::operator++()
 {
-    // iterator cannot be incremented
-    if (object_ == nullptr)
+    if (object_ != nullptr)
     {
-        return *this;
+        switch (object_->type_)
+        {
+            case (json::value_t::array):
+            {
+                std::advance(*vi_, 1);
+                invalid = (*vi_ == object_->value_.array->end());
+                break;
+            }
+            case (json::value_t::object):
+            {
+                std::advance(*oi_, 1);
+                invalid = (*oi_ == object_->value_.object->end());
+                break;
+            }
+            default:
+            {
+                invalid = true;
+                break;
+            }
+        }
     }
 
-    switch (object_->type_)
-    {
-        case (json::value_t::array):
-        {
-            if (++(*vi_) == object_->value_.array->end())
-            {
-                object_ = nullptr;
-            }
-            break;
-        }
-        case (json::value_t::object):
-        {
-            if (++(*oi_) == object_->value_.object->end())
-            {
-                object_ = nullptr;
-            }
-            break;
-        }
-        default:
-        {
-            object_ = nullptr;
-        }
-    }
     return *this;
 }
 
 const json& json::const_iterator::operator*() const
 {
-    // dereferencing end() is an error
-    if (object_ == nullptr)
+    if (object_ == nullptr or invalid)
     {
-        throw std::runtime_error("cannot get value");
+        throw std::out_of_range("cannot get value");
     }
 
     switch (object_->type_)
@@ -1811,10 +1732,9 @@ const json& json::const_iterator::operator*() const
 
 const json* json::const_iterator::operator->() const
 {
-    // dereferencing end() is an error
-    if (object_ == nullptr)
+    if (object_ == nullptr or invalid)
     {
-        throw std::runtime_error("cannot get value");
+        throw std::out_of_range("cannot get value");
     }
 
     switch (object_->type_)
@@ -1836,20 +1756,17 @@ const json* json::const_iterator::operator->() const
 
 std::string json::const_iterator::key() const
 {
-    if (object_ != nullptr and object_->type_ == json::value_t::object)
+    if (object_ == nullptr or invalid or object_->type_ != json::value_t::object)
     {
-        return (*oi_)->first;
-    }
-    else
-    {
-        throw std::out_of_range("cannot get key");
+        throw std::out_of_range("cannot get value");
     }
+
+    return (*oi_)->first;
 }
 
 const json& json::const_iterator::value() const
 {
-    // dereferencing end() is an error
-    if (object_ == nullptr)
+    if (object_ == nullptr or invalid)
     {
         throw std::out_of_range("cannot get value");
     }
diff --git a/src/json.h b/src/json.h
index fb9a1f7d..a0a8b25e 100644
--- a/src/json.h
+++ b/src/json.h
@@ -42,11 +42,12 @@ due to alignment.
 */
 class json
 {
-  public:
+  private:
     // forward declaration to friend this class
     class iterator;
     class const_iterator;
 
+  public:
     // container types
     using value_type = json;
     using reference = json&;
@@ -371,15 +372,15 @@ class json
     /// the payload
     value value_ {};
 
-  public:
+  private:
     /// an iterator
-    class iterator : public std::iterator<std::forward_iterator_tag, json>
+    class iterator : private std::iterator<std::forward_iterator_tag, json>
     {
         friend class json;
         friend class json::const_iterator;
       public:
         iterator() = default;
-        iterator(json*);
+        iterator(json*, bool);
         iterator(const iterator&);
         ~iterator();
 
@@ -402,16 +403,18 @@ class json
         array_t::iterator* vi_ = nullptr;
         /// an iterator for JSON objects
         object_t::iterator* oi_ = nullptr;
+        /// whether iterator points to a valid object
+        bool invalid = true;
     };
 
     /// a const iterator
-    class const_iterator : public std::iterator<std::forward_iterator_tag, const json>
+    class const_iterator : private std::iterator<std::forward_iterator_tag, const json>
     {
         friend class json;
 
       public:
         const_iterator() = default;
-        const_iterator(const json*);
+        const_iterator(const json*, bool);
         const_iterator(const const_iterator&);
         const_iterator(const json::iterator&);
         ~const_iterator();
@@ -435,6 +438,8 @@ class json
         array_t::const_iterator* vi_ = nullptr;
         /// an iterator for JSON objects
         object_t::const_iterator* oi_ = nullptr;
+        /// whether iterator reached past the end
+        bool invalid = true;
     };
 
   private:
diff --git a/test/json_unit.cc b/test/json_unit.cc
index a5f346ba..6596d4c0 100644
--- a/test/json_unit.cc
+++ b/test/json_unit.cc
@@ -1371,35 +1371,35 @@ TEST_CASE("Iterators")
     CHECK(* j7.begin() == json("hello"));
     CHECK(* j7_const.begin() == json("hello"));
 
-    CHECK_THROWS_AS(* j1.end(), std::runtime_error);
-    CHECK_THROWS_AS(* j1.cend(), std::runtime_error);
-    CHECK_THROWS_AS(* j2.end(), std::runtime_error);
-    CHECK_THROWS_AS(* j2.cend(), std::runtime_error);
-    CHECK_THROWS_AS(* j3.end(), std::runtime_error);
-    CHECK_THROWS_AS(* j3.cend(), std::runtime_error);
-    CHECK_THROWS_AS(* j4.end(), std::runtime_error);
-    CHECK_THROWS_AS(* j4.cend(), std::runtime_error);
-    CHECK_THROWS_AS(* j5.end(), std::runtime_error);
-    CHECK_THROWS_AS(* j5.cend(), std::runtime_error);
-    CHECK_THROWS_AS(* j6.end(), std::runtime_error);
-    CHECK_THROWS_AS(* j6.cend(), std::runtime_error);
-    CHECK_THROWS_AS(* j7.end(), std::runtime_error);
-    CHECK_THROWS_AS(* j7.cend(), std::runtime_error);
+    CHECK_THROWS_AS(* j1.end(), std::out_of_range);
+    CHECK_THROWS_AS(* j1.cend(), std::out_of_range);
+    CHECK_THROWS_AS(* j2.end(), std::out_of_range);
+    CHECK_THROWS_AS(* j2.cend(), std::out_of_range);
+    CHECK_THROWS_AS(* j3.end(), std::out_of_range);
+    CHECK_THROWS_AS(* j3.cend(), std::out_of_range);
+    CHECK_THROWS_AS(* j4.end(), std::out_of_range);
+    CHECK_THROWS_AS(* j4.cend(), std::out_of_range);
+    CHECK_THROWS_AS(* j5.end(), std::out_of_range);
+    CHECK_THROWS_AS(* j5.cend(), std::out_of_range);
+    CHECK_THROWS_AS(* j6.end(), std::out_of_range);
+    CHECK_THROWS_AS(* j6.cend(), std::out_of_range);
+    CHECK_THROWS_AS(* j7.end(), std::out_of_range);
+    CHECK_THROWS_AS(* j7.cend(), std::out_of_range);
 
-    CHECK_THROWS_AS(* j1_const.end(), std::runtime_error);
-    CHECK_THROWS_AS(* j1_const.cend(), std::runtime_error);
-    CHECK_THROWS_AS(* j2_const.end(), std::runtime_error);
-    CHECK_THROWS_AS(* j2_const.cend(), std::runtime_error);
-    CHECK_THROWS_AS(* j3_const.end(), std::runtime_error);
-    CHECK_THROWS_AS(* j3_const.cend(), std::runtime_error);
-    CHECK_THROWS_AS(* j4_const.end(), std::runtime_error);
-    CHECK_THROWS_AS(* j4_const.cend(), std::runtime_error);
-    CHECK_THROWS_AS(* j5_const.end(), std::runtime_error);
-    CHECK_THROWS_AS(* j5_const.cend(), std::runtime_error);
-    CHECK_THROWS_AS(* j6_const.end(), std::runtime_error);
-    CHECK_THROWS_AS(* j6_const.cend(), std::runtime_error);
-    CHECK_THROWS_AS(* j7_const.end(), std::runtime_error);
-    CHECK_THROWS_AS(* j7_const.cend(), std::runtime_error);
+    CHECK_THROWS_AS(* j1_const.end(), std::out_of_range);
+    CHECK_THROWS_AS(* j1_const.cend(), std::out_of_range);
+    CHECK_THROWS_AS(* j2_const.end(), std::out_of_range);
+    CHECK_THROWS_AS(* j2_const.cend(), std::out_of_range);
+    CHECK_THROWS_AS(* j3_const.end(), std::out_of_range);
+    CHECK_THROWS_AS(* j3_const.cend(), std::out_of_range);
+    CHECK_THROWS_AS(* j4_const.end(), std::out_of_range);
+    CHECK_THROWS_AS(* j4_const.cend(), std::out_of_range);
+    CHECK_THROWS_AS(* j5_const.end(), std::out_of_range);
+    CHECK_THROWS_AS(* j5_const.cend(), std::out_of_range);
+    CHECK_THROWS_AS(* j6_const.end(), std::out_of_range);
+    CHECK_THROWS_AS(* j6_const.cend(), std::out_of_range);
+    CHECK_THROWS_AS(* j7_const.end(), std::out_of_range);
+    CHECK_THROWS_AS(* j7_const.cend(), std::out_of_range);
 
     // operator ->
     CHECK(j1.begin()->type() == json::value_t::number);
@@ -1432,35 +1432,35 @@ TEST_CASE("Iterators")
     CHECK(j7_const.begin()->type() == json::value_t::string);
     CHECK(j7_const.cbegin()->type() == json::value_t::string);
 
-    CHECK_THROWS_AS(j1.end()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j1.cend()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j2.end()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j2.cend()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j3.end()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j3.cend()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j4.end()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j4.cend()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j5.end()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j5.cend()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j6.end()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j6.cend()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j7.end()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j7.cend()->type(), std::runtime_error);
+    CHECK_THROWS_AS(j1.end()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j1.cend()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j2.end()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j2.cend()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j3.end()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j3.cend()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j4.end()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j4.cend()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j5.end()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j5.cend()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j6.end()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j6.cend()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j7.end()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j7.cend()->type(), std::out_of_range);
 
-    CHECK_THROWS_AS(j1_const.end()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j1_const.cend()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j2_const.end()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j2_const.cend()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j3_const.end()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j3_const.cend()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j4_const.end()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j4_const.cend()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j5_const.end()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j5_const.cend()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j6_const.end()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j6_const.cend()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j7_const.end()->type(), std::runtime_error);
-    CHECK_THROWS_AS(j7_const.cend()->type(), std::runtime_error);
+    CHECK_THROWS_AS(j1_const.end()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j1_const.cend()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j2_const.end()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j2_const.cend()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j3_const.end()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j3_const.cend()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j4_const.end()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j4_const.cend()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j5_const.end()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j5_const.cend()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j6_const.end()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j6_const.cend()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j7_const.end()->type(), std::out_of_range);
+    CHECK_THROWS_AS(j7_const.cend()->type(), std::out_of_range);
 
     // value
     CHECK(j1.begin().value().type() == json::value_t::number);