diff --git a/include/libcamera/base/utils.h b/include/libcamera/base/utils.h
index b33a4c644a87..9835fd7980dc 100644
--- a/include/libcamera/base/utils.h
+++ b/include/libcamera/base/utils.h
@@ -430,6 +430,21 @@ scope_exit(EF) -> scope_exit<EF>;
 
 #endif /* __DOXYGEN__ */
 
+namespace details {
+
+struct defopt_t {
+	template<typename T>
+	constexpr operator T() const
+	{
+		static_assert(std::is_default_constructible_v<T>);
+		return T{};
+	}
+};
+
+} /* namespace details */
+
+constexpr details::defopt_t defopt;
+
 #ifndef __DOXYGEN__
 std::ostream &operator<<(std::ostream &os, const Duration &d);
 #endif
diff --git a/src/libcamera/base/utils.cpp b/src/libcamera/base/utils.cpp
index 4ab2bd863e11..eca3efddb2e3 100644
--- a/src/libcamera/base/utils.cpp
+++ b/src/libcamera/base/utils.cpp
@@ -685,6 +685,23 @@ void ScopeExitActions::release()
 	actions_.clear();
 }
 
+/**
+ * \var defopt
+ * \brief Constant used with std::optional::value_or() to create a
+ * default-constructed object
+ *
+ * The std::optional<T>::value_or(U &&default_value) function returns the
+ * contained value if available, or \a default_value if the std::optional has no
+ * value. If the desired default value is a default-constructed T, the obvious
+ * option is to call std::optional<T>::value_or(T{}). This approach however
+ * constructs the \a default_value T{} even if the std::optional instance has a
+ * value, which impacts efficiency.
+ *
+ * The defopt variable solves this issue by providing a value that can be passed
+ * to std::optional<T>::value_or() and get implicitly converted to a
+ * default-constructed T.
+ */
+
 #ifndef __DOXYGEN__
 std::ostream &operator<<(std::ostream &os, const Duration &d)
 {
diff --git a/test/utils.cpp b/test/utils.cpp
index b5ce94e5e912..eba3ed9fbf43 100644
--- a/test/utils.cpp
+++ b/test/utils.cpp
@@ -178,6 +178,55 @@ protected:
 		return TestPass;
 	}
 
+	int testDefopt()
+	{
+		static bool defaultConstructed = false;
+
+		struct ValueType {
+			ValueType()
+				: value_(-1)
+			{
+				defaultConstructed = true;
+			}
+
+			ValueType(int value)
+				: value_(value)
+			{
+			}
+
+			int value_;
+		};
+
+		/*
+		 * Test that utils::defopt doesn't cause default-construction
+		 * of a ValueType instance when value_or(utils::defopt) is
+		 * called on a std::optional that has a value.
+		 */
+		std::optional<ValueType> opt = ValueType(0);
+		ValueType value = opt.value_or(utils::defopt);
+
+		if (defaultConstructed || value.value_ != 0) {
+			std::cerr << "utils::defopt didn't prevent default construction"
+				  << std::endl;
+			return TestFail;
+		}
+
+		/*
+		 * Then test that the ValueType is correctly default-constructed
+		 * when the std::optional has no value.
+		 */
+		opt = std::nullopt;
+		value = opt.value_or(utils::defopt);
+
+		if (!defaultConstructed || value.value_ != -1) {
+			std::cerr << "utils::defopt didn't cause default construction"
+				  << std::endl;
+			return TestFail;
+		}
+
+		return TestPass;
+	}
+
 	int run()
 	{
 		/* utils::hex() test. */
@@ -332,6 +381,10 @@ protected:
 		if (testDuration() != TestPass)
 			return TestFail;
 
+		/* utils::defopt test. */
+		if (testDefopt() != TestPass)
+			return TestFail;
+
 		return TestPass;
 	}
 };
