With dynamic variable binding, there are no side effects, because the LET is undone as soon as its scope is exited.
Think of dynamic variables as constituting merely the implicit passing of a symbol->value hash table as an argument to every function call, and a LET as a local modification to this hash table.
So:
(defvar a 10)
(defun x ()
(if (= a 0)
t
(let ((a (- a 1)))
(x))))
behaves the same as:
(defun x (&optional (a 10))
(if (= a 0)
t
(x (- a 1))))
Think of dynamic variables as constituting merely the implicit passing of a symbol->value hash table as an argument to every function call, and a LET as a local modification to this hash table.
So:
(defvar a 10)
(defun x () (if (= a 0) t (let ((a (- a 1))) (x))))
behaves the same as:
(defun x (&optional (a 10)) (if (= a 0) t (x (- a 1))))