Clojure Programming Language Guide — Lisp on the JVM
Clojure is a modern Lisp dialect that runs on the Java Virtual Machine — combining functional programming with immutable data structures, a rich set of concurrency primitives, and seamless access to the entire Java ecosystem.
What You’ll Learn
- Homoiconicity and the code-as-data philosophy
- REPL-driven interactive development
- Immutable data structures (lists, vectors, maps, sets)
- Concurrency with atoms, refs, agents, and core.async
- Interoperability with Java libraries
Why It Matters
Clojure brings the power of Lisp to the JVM — you get the flexibility of a dynamic, interactive language with access to every Java library ever written. Durga Antivirus Pro uses Clojure for its rule-based threat analysis engine where immutable data guarantees thread safety without locks. Companies like Walmart, Netflix, and Apple use Clojure for high-concurrency systems. The REPL workflow means you can develop live against a running system — changing code without restarting — which is transformative for productivity.
Learning Path
flowchart LR
A[Clojure Syntax & REPL<br/>You are here] --> B[Immutable Data Structures]
B --> C[Functional Programming]
C --> D[Concurrency & State]
D --> E[Java Interop & Real Apps]
Your First Clojure Program
;; hello.clj
(defn greet [name]
(str "Hello, " name "!"))
(println (greet "Clojure"))
;; Run with: clojure -M hello.cljclojure -M hello.clj
# Hello, Clojure!Code Syntax and Homoiconicity
In Clojure, code is data — programs are written as Clojure data structures.
;; Lists (function calls): (operator operand operand...)
(+ 1 2 3) ;; => 6
(str "Hello" " " "World") ;; => "Hello World"
;; Vectors (indexed, like arrays)
[1 2 3 4 5]
;; Maps (key-value pairs)
{:name "Alice" :age 30 :language "Clojure"}
;; Sets
#{:a :b :c}
;; Keywords (self-evaluating, used as keys)
:name :age
;; Homoiconicity demonstration
(def my-code '(+ 1 2 3))
(eval my-code) ;; => 6
;; We can manipulate code as data
(defn double-args [code]
(map (fn [x] (if (number? x) (* 2 x) x)) code))
(double-args '(+ 1 2 3)) ;; => (+ 2 4 6)
(eval (double-args '(+ 1 2 3))) ;; => 126
(+ 2 4 6)
12Immutable Data Structures
All Clojure data structures are immutable and persistent — they share structure for efficiency.
;; Vectors
(def v [1 2 3 4 5])
(conj v 6) ;; => [1 2 3 4 5 6] (original v unchanged)
(get v 2) ;; => 3
(assoc v 0 100) ;; => [100 2 3 4 5]
(subvec v 1 3) ;; => [2 3]
;; Maps
(def m {:name "Bob" :age 25 :city "Berlin"})
(assoc m :job "Engineer") ;; => {:name "Bob", :age 25, :city "Berlin", :job "Engineer"}
(dissoc m :city) ;; => {:name "Bob", :age 25}
(update m :age inc) ;; => {:name "Bob", :age 26, :city "Berlin"}
(merge m {:age 26 :country "DE"}) ;; => {:name "Bob", :age 26, :city "Berlin", :country "DE"}
;; Lists (linked lists, efficient for head access)
(def lst '(1 2 3 4 5))
(peek lst) ;; => 1
(pop lst) ;; => (2 3 4 5)
(conj lst 0) ;; => (0 1 2 3 4 5)Functional Programming
;; Higher-order functions
(map inc [1 2 3 4 5]) ;; => (2 3 4 5 6)
(filter even? (range 10)) ;; => (0 2 4 6 8)
(reduce + [1 2 3 4 5]) ;; => 15
;; Threading macros (pipeline data through functions)
(->> (range 100)
(filter odd?)
(map #(* % %))
(take 5))
;; => (1 9 25 49 81)
;; Partial application
(def add5 (partial + 5))
(add5 10) ;; => 15
;; Composing functions
(def process (comp str inc (partial * 2)))
(process 5) ;; => "11" (str (inc (* 2 5)))
;; Destructuring
(defn print-person [{:keys [name age city]}]
(println (str name " is " age " years old from " city)))
(print-person {:name "Alice" :age 30 :city "Paris"})
;; Alice is 30 years old from ParisAlice is 30 years old from ParisConcurrency — Atoms, Refs, Agents
Clojure provides separate concurrency primitives for different use cases.
;; Atoms — synchronous, uncoordinated state
(def counter (atom 0))
(swap! counter inc) ;; atomically increment
(swap! counter + 5) ;; atomically add 5
(reset! counter 0) ;; reset to 0
(println @counter) ;; 0
;; Refs — coordinated, transactional state
(def account-a (ref 1000))
(def account-b (ref 500))
(defn transfer [from to amount]
(dosync
(alter from - amount)
(alter to + amount)))
(transfer account-a account-b 200)
(println @account-a) ;; 800
(println @account-b) ;; 700
;; Agents — asynchronous, independent state
(def log-agent (agent []))
(send log-agent conj "Started")
(send log-agent conj "Processing...")
(send log-agent conj "Completed")
(await log-agent) ;; wait for all sends to complete
(println @log-agent)0
800
700
["Started" "Processing..." "Completed"]Java Interop
Clojure can call any Java code directly.
;; Import Java classes
(import 'java.util.Date
'java.text.SimpleDateFormat
'javax.swing.JFrame
'javax.swing.JLabel
'java.awt.Font)
;; Call Java methods
(.toUpperCase "hello") ;; => "HELLO"
(.startsWith "Clojure" "Clo") ;; => true
(Math/sqrt 144) ;; => 12.0
;; Create Java objects
(def date (Date.))
(println date) ;; current date
;; Use Java collections
(def list (java.util.ArrayList.))
(.add list "item1")
(.add list "item2")
(println (.size list)) ;; 2
;; Create a Swing window
(defn show-window []
(doto (JFrame. "Hello from Clojure")
(.setSize 400 300)
(.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
(.add (doto (JLabel. "Hello, Clojure Swing!" {})
(.setHorizontalAlignment JLabel/CENTER)
(.setFont (Font. "Serif" Font/BOLD 24))))
(.setVisible true)))
;; (show-window) ;; uncomment to runHELLO
true
12.0
2REPL-Driven Development
;; In a REPL session:
;;
;; user=> (+ 1 2 3)
;; 6
;;
;; user=> (defn square [x] (* x x))
;; #'user/square
;;
;; user=> (square 5)
;; 25
;;
;; user=> (doc map)
;; clojure.core/map
;; ([f coll] [f c1 c2 ...])
;; Returns a lazy sequence...
;;
;; user=> (require '[clojure.string :as str])
;; nil
;;
;; user=> (str/join ", " [1 2 3])
;; "1, 2, 3"Common Mistakes
1. Forgetting parentheses around function calls
println "hello" throws an error. In Clojure, everything is (function arg arg) — the parentheses are not optional.
2. Using = for assignment instead of comparison
(= x 5) checks equality. Clojure has no variable reassignment in the traditional sense — use def or let for bindings.
3. Mutating state without concurrency primitives
(def m {:a 1}) then (assoc m :a 2) creates a new map — m still points to {:a 1}. Use atoms for mutable references.
4. Excessive nesting (the “Lisp parens” problem)
Deeply nested code is hard to read. Use threading macros (->, ->>, as->) to linearize pipelines.
5. Assuming vectors and lists are interchangeable
Vectors are indexed (O(1) access). Lists are sequential (O(n) access). conj adds to the end of vectors but the front of lists.
6. Forgetting Java interop requires proper imports
Java classes need (import 'package.ClassName) or :import in ns. Using unimported classes causes class-not-found errors.
Practice Questions
What is homoiconicity? Code is represented as data structures of the language itself. Clojure code is written as lists of symbols, which can be manipulated and transformed like any other data.
How do atoms differ from refs? Atoms are for uncoordinated, synchronous state changes. Refs are for coordinated, transactional changes across multiple state points. Both ensure thread safety without locks.
What is the threading macro
->>? Thread-last macro: takes the result of each expression and inserts it as the LAST argument of the next expression. Flattens deeply nested function calls into readable pipelines.How does Clojure handle immutability efficiently? Persistent data structures share structure across versions. When you “modify” a vector, most of the old vector is reused. This gives O(log n) operations instead of O(n).
What is REPL-driven development? Developing against a running process: you connect a REPL to the application, evaluate code changes on the fly, and see results immediately — without restarting. This enables interactive exploration and rapid iteration.
Challenge: Write a Clojure program that reads a log file, parses lines matching a regex pattern, counts occurrences grouped by severity level (INFO, WARN, ERROR), and prints a summary sorted by count.
Mini Project — Simple HTTP Server
(ns web-server
(:import (java.net ServerSocket InetAddress)
(java.io BufferedReader InputStreamReader PrintWriter)))
(defn handle-client [client-socket]
(try
(let [input (BufferedReader. (InputStreamReader. (.getInputStream client-socket)))
output (PrintWriter. (.getOutputStream client-socket) true)
request (.readLine input)]
(println "Request:" request)
;; Read headers
(loop [line (.readLine input)]
(when (and line (not (.isEmpty line)))
(println "Header:" line)
(recur (.readLine input))))
;; Send HTTP response
(.println output "HTTP/1.1 200 OK")
(.println output "Content-Type: text/html")
(.println output "")
(.println output "<!DOCTYPE html>")
(.println output "<html><body>")
(.println output "<h1>Clojure HTTP Server</h1>")
(.println output (str "<p>Requested: " request "</p>"))
(.println output "<ul>")
(dotimes [i 5]
(.println output (str "<li>Item " i "</li>")))
(.println output "</ul></body></html>"))
(finally
(.close client-socket))))
(defn start-server [port]
(let [server (ServerSocket. port)]
(println (str "Server running on http://localhost:" port))
(println "Press Ctrl+C to stop")
(try
(while true
(let [client (.accept server)]
(future (handle-client client))))
(finally
(.close server)))))
;; Start the server
(comment
(start-server 8080)
;; Visit http://localhost:8080 in your browser
;; Each request spawns a future (thread) to handle it
)FAQ
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro