From the documentation:
Namespaces are mappings from simple (unqualified) symbols to Vars and/or Classes. Vars can be interned in a namespace, using def or any of its variants, in which case they have a simple symbol for a name and a reference to their containing namespace, and the namespace maps that symbol to the same var.
Vars:
Vars provide a mechanism to refer to a mutable storage location that can be dynamically rebound (to a new storage location) on a per-thread basis.
So, both namespaces and vars are mutable. In the example you give, the first occurrence of def
creates a new Var and interns it (i.e. adds a new mapping to it) in the current namespace. The second occurrence of def
resets the root binding of the existing Var, without altering the namespace mappings.
When you evaluate some code that contains the symbol of a var that you've defined (assuming it's not shadowed by any local lexical bindings), the compiler first looks up the var mapped to that symbol, and then gets the current value for that var.