let is a fundamental part of Clojure. Whereas
def creates a global variable,
let creates a local variable.
(def x 5) (println x) ; => 5 ; nil (let [x 2] (println x)) ; => 2 ; nil (println x) ; => 5 ; nil
x in this example never actually gets changed.
x just refers to something different inside of our
let binding. This can be a useful way to avoid repetition inside a function.
This is incredibly useful. Having too many global variables can lead to nasty bugs and unintended behaviour.
(def x 5) (defn add-5 [y] (+ x y)) (add-5 5) ; => 10 (defn change-x  (def x 6)) (change-x) ; => nil (add-5 5) ; => 11
Uh oh! That’s not adding 5 anymore! Of course, this example is a bit silly, but using too many global variables can lead to bugs that are just as scary as this one.
Note: We aren’t really reassigning
x here, like you would in a C-like language. We’re just creating a new variable that happens to also be called x. This is a very, very, very bad idea.
let can also define multiple variables at once, and can assign variables to expressions.
(let [spam "foo" ham (str "b" "ar")] ; str is a function that concatenates strings (println spam ham)) ; or converts variables into strings. ; => foo bar ; nil