Skip to content

Instantly share code, notes, and snippets.

@phondanai
Created February 4, 2026 07:38
Show Gist options
  • Select an option

  • Save phondanai/6fe5b6a99e8afec30fc38c23f09ada4b to your computer and use it in GitHub Desktop.

Select an option

Save phondanai/6fe5b6a99e8afec30fc38c23f09ada4b to your computer and use it in GitHub Desktop.

Clojure Survival Guide: Open Source & Professional Development

A practical guide to the Clojure knowledge you need for open source contributions and professional web/data processing work.

Table of Contents

  1. Core Language Fundamentals
  2. Essential Libraries & Ecosystem
  3. Web Development Essentials
  4. Data Processing
  5. Tooling & Development Workflow
  6. Reading & Understanding Code
  7. Common Patterns You'll Encounter

Core Language Fundamentals

1. Data Structures (Critical)

You must be comfortable with Clojure's immutable data structures:

;; Vectors (indexed, like arrays)
[1 2 3 4]
(get [10 20 30] 1)  ; => 20
(conj [1 2] 3)      ; => [1 2 3]

;; Maps (key-value pairs)
{:name "Alice" :age 30}
(get {:a 1 :b 2} :a)        ; => 1
({:a 1 :b 2} :a)            ; maps are functions
(:a {:a 1 :b 2})            ; keywords are functions too!
(assoc {:a 1} :b 2)         ; => {:a 1 :b 2}
(dissoc {:a 1 :b 2} :b)     ; => {:a 1}

;; Lists (linked lists, used for code)
'(1 2 3 4)
(cons 0 '(1 2 3))   ; => (0 1 2 3)

;; Sets (unique values)
#{1 2 3}
(conj #{1 2} 3)     ; => #{1 2 3}
(contains? #{1 2 3} 2)  ; => true

2. Sequences & Sequence Operations (Critical)

Almost everything in Clojure can be treated as a sequence:

;; Core sequence functions you'll see everywhere
(map inc [1 2 3])           ; => (2 3 4)
(filter even? [1 2 3 4])    ; => (2 4)
(reduce + [1 2 3 4])        ; => 10
(take 3 [1 2 3 4 5])        ; => (1 2 3)
(drop 2 [1 2 3 4 5])        ; => (3 4 5)

;; Lazy sequences (evaluated on demand)
(take 5 (range))            ; => (0 1 2 3 4) - infinite sequence!
(take 5 (repeat "x"))       ; => ("x" "x" "x" "x" "x")

;; Threading macros (code readability)
(->> [1 2 3 4 5]
     (filter odd?)
     (map #(* % 2))
     (reduce +))            ; => 18

;; Same as: (reduce + (map #(* % 2) (filter odd? [1 2 3 4 5])))

3. Functions (Critical)

;; Named functions
(defn greet [name]
  (str "Hello, " name))

;; Multi-arity functions
(defn greet
  ([name] (greet name "Hello"))
  ([name greeting] (str greeting ", " name)))

;; Anonymous functions
(fn [x] (* x 2))
#(* % 2)                    ; shorthand (% is first arg, %2 is second)

;; Higher-order functions
(defn apply-twice [f x]
  (f (f x)))

(apply-twice inc 5)         ; => 7

;; Partial application
(def add-ten (partial + 10))
(add-ten 5)                 ; => 15

4. Destructuring (Very Important)

You'll see this pattern constantly in real code:

;; Vector destructuring
(let [[a b c] [1 2 3]]
  (+ a b c))                ; => 6

;; Map destructuring
(defn process-user [{:keys [name age email]}]
  (println name age email))

(process-user {:name "Bob" :age 25 :email "bob@example.com"})

;; With defaults
(defn greet [{:keys [name] :or {name "Guest"}}]
  (str "Hello, " name))

;; Nested destructuring
(let [{:keys [user] {:keys [address]} :user} 
      {:user {:name "Alice" :address "123 Main St"}}]
  address)

5. Let Bindings & Scope

(let [x 10
      y 20
      z (+ x y)]
  (* z 2))                  ; => 60

;; Letfn for mutually recursive functions
(letfn [(even? [n]
          (if (zero? n) true (odd? (dec n))))
        (odd? [n]
          (if (zero? n) false (even? (dec n))))]
  (even? 4))

6. Conditionals & Logic

;; if (returns nil if false and no else clause)
(if (even? 4)
  "yes"
  "no")

;; when (for side effects, implicit do)
(when (seq items)
  (println "Processing items")
  (process items))

;; cond (multiple conditions)
(cond
  (< x 0) "negative"
  (> x 0) "positive"
  :else "zero")

;; case (efficient dispatch on constants)
(case x
  1 "one"
  2 "two"
  "other")

;; Truthy/falsy: only nil and false are falsy
(if [] "truthy" "falsy")    ; => "truthy" (empty collections are truthy!)

Essential Libraries & Ecosystem

Libraries You'll Encounter

Build Tools:

  • Leiningen (most common): project.clj configuration
  • tools.deps (deps.edn): Modern, official tool
  • Boot: Alternative build tool

Testing:

  • clojure.test - Built-in testing
  • midje - Alternative testing framework
  • test.check - Property-based testing

Web:

  • Ring/Compojure/Reitit - HTTP handling & routing
  • Pedestal - Full web framework
  • re-frame/Reagent - ClojureScript frontend

Data:

  • clojure.data.json / cheshire - JSON
  • clojure.data.csv - CSV
  • clojure.java.jdbc / next.jdbc - Database access
  • honeysql - SQL generation

Async:

  • core.async - CSP-style concurrency

Reading project.clj (Leiningen)

(defproject myapp "0.1.0"
  :description "My Application"
  :dependencies [[org.clojure/clojure "1.11.1"]
                 [ring/ring-core "1.9.5"]
                 [compojure "1.7.0"]
                 [cheshire "5.11.0"]]
  :main ^:skip-aot myapp.core
  :target-path "target/%s"
  :profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})

Reading deps.edn (tools.deps)

{:paths ["src" "resources"]
 :deps {org.clojure/clojure {:mvn/version "1.11.1"}
        ring/ring-core {:mvn/version "1.9.5"}
        metosin/reitit {:mvn/version "0.5.18"}}
 :aliases {:dev {:extra-deps {nrepl/nrepl {:mvn/version "0.9.0"}}}}}

Web Development Essentials

1. Ring (HTTP Abstraction)

Ring is the foundation - it's like Rack for Ruby or WSGI for Python:

;; A Ring handler is just a function: request -> response
(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body "Hello World"})

;; Request map structure
{:server-port 8080
 :server-name "localhost"
 :remote-addr "127.0.0.1"
 :uri "/users/123"
 :query-string "page=1"
 :scheme :http
 :request-method :get
 :headers {"host" "localhost:8080"
           "accept" "application/json"}
 :body nil}

;; Middleware (functions that wrap handlers)
(defn wrap-logging [handler]
  (fn [request]
    (println "Request:" (:uri request))
    (handler request)))

(def app
  (-> handler
      wrap-logging
      wrap-params))  ; Ring middleware for parsing query params

2. Routing with Compojure

(require '[compojure.core :refer [defroutes GET POST]])
(require '[compojure.route :as route])

(defroutes app-routes
  (GET "/" [] "Welcome")
  (GET "/users/:id" [id] (str "User " id))
  (POST "/users" {params :params} 
    (create-user params))
  (route/not-found "Not Found"))

;; With destructuring
(GET "/search" [q limit]
  (search-items q limit))

3. Modern Routing with Reitit

(require '[reitit.ring :as ring])

(def app
  (ring/ring-handler
    (ring/router
      [["/api"
        ["/users" {:get user-list
                   :post create-user}]
        ["/users/:id" {:get user-detail
                       :put update-user
                       :delete delete-user}]]])))

4. JSON Handling

(require '[cheshire.core :as json])

;; Parse JSON
(json/parse-string "{\"name\":\"Alice\"}" true)
;; => {:name "Alice"}

;; Generate JSON
(json/generate-string {:name "Alice" :age 30})
;; => "{\"name\":\"Alice\",\"age\":30}"

;; Common pattern in Ring handlers
(defn json-response [data]
  {:status 200
   :headers {"Content-Type" "application/json"}
   :body (json/generate-string data)})

5. Database Access (next.jdbc)

(require '[next.jdbc :as jdbc])
(require '[next.jdbc.sql :as sql])

(def db {:dbtype "postgresql"
         :dbname "mydb"
         :user "dbuser"
         :password "secret"})

;; Query
(sql/query db ["SELECT * FROM users WHERE active = ?" true])

;; Insert
(sql/insert! db :users {:name "Alice" :email "alice@example.com"})

;; Update
(sql/update! db :users {:active false} {:id 123})

;; Using HoneySQL for query building
(require '[honey.sql :as sql])

(sql/format {:select [:*]
             :from [:users]
             :where [:= :active true]
             :order-by [[:created-at :desc]]
             :limit 10})
;; => ["SELECT * FROM users WHERE active = ? ORDER BY created_at DESC LIMIT ?" true 10]

Data Processing

1. Transforming Data (Essential Skill)

;; Common pattern: process collection of maps
(def users
  [{:id 1 :name "Alice" :age 30 :active true}
   {:id 2 :name "Bob" :age 25 :active false}
   {:id 3 :name "Carol" :age 35 :active true}])

;; Filter and extract
(->> users
     (filter :active)
     (map :name))
;; => ("Alice" "Carol")

;; Group by
(group-by :active users)
;; => {true [{:id 1...} {:id 3...}], false [{:id 2...}]}

;; Index by key
(into {} (map (juxt :id identity) users))
;; => {1 {:id 1 :name "Alice"...}, 2 {...}, 3 {...}}

;; Update values
(map #(update % :age inc) users)

2. Transducers (Performance)

Transducers compose transformations without creating intermediate collections:

;; Without transducers (creates intermediate collections)
(->> (range 1000000)
     (filter odd?)
     (map #(* % 2))
     (take 10))

;; With transducers (no intermediate collections)
(into []
      (comp (filter odd?)
            (map #(* % 2))
            (take 10))
      (range 1000000))

;; Common transducer usage
(transduce
  (comp (filter even?) (map inc))
  +
  (range 10))
;; => 25

3. Processing Nested Data

;; Update nested values
(def data {:user {:profile {:name "Alice"}}})

(assoc-in data [:user :profile :name] "Bob")
(update-in data [:user :profile :name] str " Smith")
(get-in data [:user :profile :name])

;; Working with vectors of maps
(def db [{:id 1 :score 10} {:id 2 :score 20}])

;; Update specific item
(map #(if (= (:id %) 2)
        (update % :score + 5)
        %)
     db)

4. Specs for Validation (Optional but Common)

(require '[clojure.spec.alpha :as s])

;; Define specs
(s/def ::name string?)
(s/def ::age (s/and int? #(> % 0)))
(s/def ::user (s/keys :req [::name ::age]))

;; Validate
(s/valid? ::user {::name "Alice" ::age 30})  ; => true
(s/explain ::user {::name "Alice" ::age -5})  ; Prints error

;; Generate test data
(require '[clojure.spec.gen.alpha :as gen])
(gen/sample (s/gen ::user))

Tooling & Development Workflow

1. REPL-Driven Development

The REPL is central to Clojure development:

;; Start REPL
lein repl
; or
clj

;; Load file
(load-file "src/myapp/core.clj")

;; Require namespace
(require '[myapp.core :as core])
(require '[myapp.core :as core] :reload)  ; Force reload

;; Common REPL helpers
(doc map)           ; Documentation
(source map)        ; Show source
(apropos "map")     ; Find functions with "map" in name
(dir clojure.string) ; List functions in namespace

2. Editor Integration

Most Clojure developers use:

  • Emacs + CIDER: Most powerful
  • VS Code + Calva: Most beginner-friendly
  • IntelliJ + Cursive: Best IDE experience
  • Vim + vim-fireplace: For vim users

Key features to learn:

  • Evaluate forms directly from editor
  • Jump to definition
  • Inline documentation
  • Structural editing (paredit/parinfer)

3. Testing

(ns myapp.core-test
  (:require [clojure.test :refer :all]
            [myapp.core :refer :all]))

(deftest addition-test
  (testing "Addition"
    (is (= 4 (+ 2 2)))
    (is (= 0 (+ 0 0)))))

(deftest user-creation-test
  (let [user (create-user {:name "Alice"})]
    (is (some? (:id user)))
    (is (= "Alice" (:name user)))))

;; Run tests
;; lein test
;; or in REPL: (run-tests)

Reading & Understanding Code

1. Namespace Organization

(ns myapp.handlers.users
  "User-related HTTP handlers"
  (:require [myapp.db.users :as db]
            [myapp.validation :as valid]
            [ring.util.response :as response]
            [cheshire.core :as json]))

;; :require - Load Clojure/ClojureScript namespaces
;; :import - Load Java classes
;; :refer - Bring symbols into current namespace
;; :as - Create alias

2. Common Idioms

;; Nil-safe navigation
(some-> user :profile :email)  ; Returns nil if any step is nil
(some->> users (filter active?) (map :name))

;; Conditional updates
(cond-> {}
  true (assoc :a 1)
  false (assoc :b 2)
  (> x 10) (assoc :c 3))
;; => {:a 1 :c 3} (if x > 10)

;; doto (call multiple methods on same object)
(doto (java.util.HashMap.)
  (.put "a" 1)
  (.put "b" 2))

3. Atoms & State Management

;; Atoms for synchronous state
(def counter (atom 0))

(swap! counter inc)           ; Atomic update
(reset! counter 100)          ; Set value
@counter                      ; Dereference to read

;; Refs for coordinated state (less common)
(def account-a (ref 1000))
(def account-b (ref 2000))

(dosync
  (alter account-a - 100)
  (alter account-b + 100))

;; Agents for async updates
(def logger (agent []))
(send logger conj "log entry")

Common Patterns You'll Encounter

1. Middleware Pattern (Web)

(defn wrap-authentication [handler]
  (fn [request]
    (if-let [user (authenticate request)]
      (handler (assoc request :user user))
      {:status 401 :body "Unauthorized"})))

2. Component Pattern (Lifecycle Management)

;; Using Stuart Sierra's component library
(defrecord Database [connection config]
  component/Lifecycle
  (start [this]
    (assoc this :connection (create-connection config)))
  (stop [this]
    (close-connection connection)
    (assoc this :connection nil)))

3. Pipeline Pattern (Data Processing)

(defn process-order [order]
  (-> order
      validate-order
      calculate-total
      apply-discounts
      save-to-db
      send-confirmation))

4. Multi-methods (Polymorphism)

(defmulti process-event :type)

(defmethod process-event :user-created [event]
  (send-welcome-email (:user event)))

(defmethod process-event :order-placed [event]
  (process-order (:order event)))

(defmethod process-event :default [event]
  (log/warn "Unknown event type" (:type event)))

5. Error Handling

;; Try/catch (similar to Java)
(try
  (risky-operation)
  (catch Exception e
    (log/error e "Operation failed")
    nil))

;; Either pattern (using libraries like cats)
(require '[cats.monad.either :as either])

(defn divide [a b]
  (if (zero? b)
    (either/left "Division by zero")
    (either/right (/ a b))))

Survival Checklist

Must Know (Day 1)

  • Basic data structures (vectors, maps, lists, sets)
  • Sequence operations (map, filter, reduce)
  • Functions and anonymous functions
  • Threading macros (-> and ->>)
  • Let bindings
  • Basic destructuring

Should Know (Week 1)

  • How to start and use REPL
  • Require and use namespaces
  • Read project.clj or deps.edn
  • Basic Ring concepts (request/response)
  • JSON parsing and generation
  • Write basic tests

Will Learn on the Job

  • Advanced macros
  • core.async
  • Transducers
  • Spec
  • Build tools details
  • Performance optimization
  • ClojureScript (if frontend work)

Quick Reference Card

;; Collections
[]                          ; vector
{}                          ; map
#{}                         ; set
'()                         ; list

;; Common functions
(first [1 2 3])            ; 1
(rest [1 2 3])             ; (2 3)
(cons 0 [1 2])             ; (0 1 2)
(conj [1 2] 3)             ; [1 2 3]
(assoc {:a 1} :b 2)        ; {:a 1 :b 2}
(dissoc {:a 1 :b 2} :a)    ; {:b 2}
(get {:a 1} :a)            ; 1
(get-in {:a {:b 1}} [:a :b]) ; 1

;; Threading
(-> x f g h)               ; (h (g (f x)))
(->> x f g h)              ; (h (g (f x)))
(some-> x f g)             ; nil-safe ->

;; Anonymous functions
#(+ % 1)                   ; (fn [x] (+ x 1))
#(+ %1 %2)                 ; (fn [x y] (+ x y))

;; Destructuring
[a b & rest]               ; vector destructuring
{:keys [name age]}         ; map destructuring

Resources

Official:

  • Clojure.org - Official documentation
  • ClojureDocs.org - Community examples
  • Clojure Cheatsheet - Quick reference

Books:

  • "Clojure for the Brave and True" - Best beginner resource
  • "Programming Clojure" - Thorough introduction
  • "Web Development with Clojure" - Web-specific

Practice:

  • 4clojure.com - Problems for practice
  • Exercism.io - Clojure track
  • Real open source projects on GitHub

Final Tips

  1. Embrace the REPL: Don't write code in files and then compile. Work interactively.

  2. Think in transformations: Clojure code is often a pipeline of data transformations.

  3. Use the ecosystem: Don't reinvent wheels. Libraries like Ring, Compojure, and next.jdbc are battle-tested.

  4. Read real code: Find popular open source Clojure projects and read their code.

  5. Don't fight immutability: Embrace it. It makes reasoning about code easier.

  6. Ask for help: The Clojure community is friendly. Use ClojureVerse, Slack, or Reddit.

You don't need to master everything to contribute or work professionally. Focus on the fundamentals, learn the common libraries in your domain (web or data), and build from there. The rest will come with practice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment