diff --git a/doc/manual/source/language/evaluation.md b/doc/manual/source/language/evaluation.md
index 980942c92..dff429776 100644
--- a/doc/manual/source/language/evaluation.md
+++ b/doc/manual/source/language/evaluation.md
@@ -74,4 +74,48 @@ in f { x = throw "error"; y = throw "error"; }
=> "ok"
```
+## Evaluation order
+
+The order in which expressions are evaluated is generally unspecified, because it does not affect successful evaluation outcomes.
+This allows more freedom for the evaluator to evolve and to evaluate efficiently.
+
+Data dependencies naturally impose some ordering constraints: a value cannot be used before it is computed.
+Beyond these constraints, the evaluator is free to choose any order.
+
+The order in which side effects such as [`builtins.trace`](@docroot@/language/builtins.md#builtins-trace) output occurs is not defined, but may be expected to follow data dependencies.
+
+In a lazy language, evaluation order is often opposite to expectations from strict languages.
+For example, in `let wrap = x: { wrapped = x; }; in wrap (1 + 2)`, the function body produces a result (`{ wrapped = ...; }`) *before* evaluating `x`.
+
+## Infinite recursion and stack overflow
+
+During evaluation, two types of errors can occur when expressions reference themselves or call functions too deeply:
+
+### Infinite recursion
+
+This error occurs when a value depends on itself through a cycle, making it impossible to compute.
+
+```nix
+let x = x; in x
+=> error: infinite recursion encountered
+```
+
+Infinite recursion happens at the value level when evaluating an expression requires evaluating the same expression again.
+
+Despite the name, infinite recursion is cheap to compute and does not involve a stack overflow.
+The cycle is finite and fairly easy to detect.
+
+### Stack overflow
+
+This error occurs when the call depth exceeds the maximum allowed limit.
+
+```nix
+let f = x: f (x + 1);
+in f 0
+=> error: stack overflow; max-call-depth exceeded
+```
+
+Stack overflow happens when too many function calls are nested without returning.
+The maximum call depth is controlled by the [`max-call-depth` setting](@docroot@/command-ref/conf-file.md#conf-max-call-depth).
+
[C API]: @docroot@/c-api.md
diff --git a/doc/manual/source/language/operators.md b/doc/manual/source/language/operators.md
index ab74e8a99..f2a7f0baa 100644
--- a/doc/manual/source/language/operators.md
+++ b/doc/manual/source/language/operators.md
@@ -23,8 +23,8 @@
| [Greater than or equal to][Comparison] | *expr* `>=` *expr* | none | 10 |
| [Equality] | *expr* `==` *expr* | none | 11 |
| Inequality | *expr* `!=` *expr* | none | 11 |
-| Logical conjunction (`AND`) | *bool* `&&` *bool* | left | 12 |
-| Logical disjunction (`OR`) | *bool* \|\| *bool* | left | 13 |
+| [Logical conjunction] (`AND`) | *bool* `&&` *bool* | left | 12 |
+| [Logical disjunction] (`OR`) | *bool* \|\| *bool* | left | 13 |
| [Logical implication] | *bool* `->` *bool* | right | 14 |
| [Pipe operator] (experimental) | *expr* `\|>` *func* | left | 15 |
| [Pipe operator] (experimental) | *func* `<\|` *expr* | right | 15 |
@@ -162,6 +162,9 @@ Update [attribute set] *attrset1* with names and values from *attrset2*.
The returned attribute set will have all of the attributes in *attrset1* and *attrset2*.
If an attribute name is present in both, the attribute value from the latter is taken.
+This operator is [strict](@docroot@/language/evaluation.md#strictness) in both *attrset1* and *attrset2*.
+That means that both arguments are evaluated to [weak head normal form](@docroot@/language/evaluation.md#values), so the attribute sets themselves are evaluated, but their attribute values are not evaluated.
+
[Update]: #update
## Comparison
@@ -185,18 +188,88 @@ All comparison operators are implemented in terms of `<`, and the following equi
## Equality
-- [Attribute sets][attribute set] and [lists][list] are compared recursively, and therefore are fully evaluated.
-- Comparison of [functions][function] always returns `false`.
+- [Attribute sets][attribute set] are compared first by attribute names and then by items until a difference is found.
+- [Lists][list] are compared first by length and then by items until a difference is found.
+- Comparison of distinct [functions][function] returns `false`, but identical functions may be subject to [value identity optimization](#value-identity-optimization).
- Numbers are type-compatible, see [arithmetic] operators.
- Floating point numbers only differ up to a limited precision.
+The `==` operator is [strict](@docroot@/language/evaluation.md#strictness) in both arguments; when comparing composite types ([attribute sets][attribute set] and [lists][list]), it is partially strict in their contained values: they are evaluated until a difference is found.
+
+### Value identity optimization
+
+Nix performs equality comparisons of nested values by pointer equality or more abstractly, _identity_.
+Nix semantics ideally do not assign a unique identity to values as they are created, but equality is an exception to this rule.
+The disputable benefit of this is that it is more efficient, and it allows cyclical structures to be compared, e.g. `let x = { x = x; }; in x == x` evaluates to `true`.
+However, as a consequence, it makes a function equal to itself when the comparison is made in a list or attribute set, in contradiction to a simple direct comparison.
+
[function]: ./syntax.md#functions
[Equality]: #equality
+## Logical conjunction
+
+> **Syntax**
+>
+> *bool1* `&&` *bool2*
+
+Logical AND. Equivalent to `if` *bool1* `then` *bool2* `else false`.
+
+This operator is [strict](@docroot@/language/evaluation.md#strictness) in *bool1*, but only evaluates *bool2* if *bool1* is `true`.
+
+> **Example**
+>
+> ```nix
+> true && false
+> => false
+>
+> false && throw "never evaluated"
+> => false
+> ```
+
+[Logical conjunction]: #logical-conjunction
+
+## Logical disjunction
+
+> **Syntax**
+>
+> *bool1* `||` *bool2*
+
+Logical OR. Equivalent to `if` *bool1* `then true` `else` *bool2*.
+
+This operator is [strict](@docroot@/language/evaluation.md#strictness) in *bool1*, but only evaluates *bool2* if *bool1* is `false`.
+
+> **Example**
+>
+> ```nix
+> true || false
+> => true
+>
+> true || throw "never evaluated"
+> => true
+> ```
+
+[Logical disjunction]: #logical-disjunction
+
## Logical implication
-Equivalent to `!`*b1* `||` *b2* (or `if` *b1* `then` *b2* `else true`)
+> **Syntax**
+>
+> *bool1* `->` *bool2*
+
+Logical implication. Equivalent to `!`*bool1* `||` *bool2* (or `if` *bool1* `then` *bool2* `else true`).
+
+This operator is [strict](@docroot@/language/evaluation.md#strictness) in *bool1*, but only evaluates *bool2* if *bool1* is `true`.
+
+> **Example**
+>
+> ```nix
+> true -> false
+> => false
+>
+> false -> throw "never evaluated"
+> => true
+> ```
[Logical implication]: #logical-implication