Browse Learn Clojure Foundations as a Java Developer

Clojure Syntax Practice

Practice Clojure syntax with Java-to-Clojure rewrites that use maps, vectors, sequence transformations, let bindings, REPL checks, and immutable update patterns.

This page turns the syntax chapter into hands-on rewrites. The goal is not to memorize isolated forms. The goal is to practice the habits that make Clojure readable: model data directly, transform immutable values, name intermediate results with let, and check small expressions in the REPL.

Work through these examples by typing them into a REPL. Clojure syntax becomes familiar faster when you see values evaluate.

Exercise 1: Replace A DTO With Data

Java often starts with a class:

1record User(long id, String email, String status) {}

Clojure can start with a map:

1(def user
2  {:user/id 42
3   :user/email "DEV@EXAMPLE.COM"
4   :user/status :active})

Now write a small normalization function:

1(require '[clojure.string :as str])
2
3(defn normalize-user [user]
4  (update user :user/email #(str/lower-case (str/trim %))))
5
6(normalize-user user)
7;; => {:user/id 42
8;;     :user/email "dev@example.com"
9;;     :user/status :active}

What to notice:

Java habit Clojure habit
Call setters or constructors Return a new map
Hide fields behind getters Use explicit keys
Mutate an object Transform a value
Put behavior on the class Write functions over data

Try it yourself: extend normalize-user so missing :user/status becomes :pending.

Exercise 2: Rewrite A Loop As A Pipeline

Java loop:

1List<String> paidEmails = new ArrayList<>();
2for (Order order : orders) {
3    if (order.status().equals("PAID")) {
4        paidEmails.add(order.customerEmail().trim().toLowerCase());
5    }
6}

Clojure data:

1(def orders
2  [{:order/id 1 :order/status :paid :order/customer-email "A@EXAMPLE.COM "}
3   {:order/id 2 :order/status :open :order/customer-email "b@example.com"}
4   {:order/id 3 :order/status :paid :order/customer-email " C@EXAMPLE.COM"}])

Clojure pipeline:

1(defn paid-emails [orders]
2  (->> orders
3       (filter #(= :paid (:order/status %)))
4       (map :order/customer-email)
5       (map #(str/lower-case (str/trim %)))
6       (into [])))
7
8(paid-emails orders)
9;; => ["a@example.com" "c@example.com"]

The ->> macro threads the collection through the last argument position. That fits sequence functions such as filter, map, and reduce.

Try it yourself: return a set of paid emails instead of a vector.

Exercise 3: Use let For Named Steps

Java local variables often describe a calculation:

1int subtotal = quantity * unitPrice;
2int discount = Math.min(subtotal, couponCents);
3int total = subtotal - discount;

Clojure let does the same without reassignment:

1(defn line-total [quantity unit-price-cents coupon-cents]
2  (let [subtotal (* quantity unit-price-cents)
3        discount (min subtotal coupon-cents)]
4    (- subtotal discount)))
5
6(line-total 3 500 200)
7;; => 1300

Use let when names clarify a calculation. Do not use it to simulate mutable variables.

Try it yourself: add tax after the discount and name the tax calculation.

Exercise 4: Update Nested Data

Clojure map updates return new values:

1(def invoice
2  {:invoice/id 1001
3   :invoice/customer {:customer/email "dev@example.com"}
4   :invoice/totals {:subtotal-cents 1000
5                    :tax-cents 130}})

Update a nested value:

1(assoc-in invoice [:invoice/customer :customer/status] :active)
2;; => {:invoice/id 1001
3;;     :invoice/customer {:customer/email "dev@example.com"
4;;                        :customer/status :active}
5;;     :invoice/totals {:subtotal-cents 1000
6;;                      :tax-cents 130}}

Transform a nested value:

1(update-in invoice [:invoice/totals :subtotal-cents] + 500)
2;; => {:invoice/id 1001
3;;     :invoice/customer {:customer/email "dev@example.com"}
4;;     :invoice/totals {:subtotal-cents 1500
5;;                      :tax-cents 130}}

Try it yourself: write a function that adds a discount under [:invoice/totals :discount-cents].

Exercise 5: Use The REPL As A Syntax Checker

When a form feels confusing, isolate it:

1(filter :order/paid? [{:order/paid? true}
2                      {:order/paid? false}
3                      {:order/id 3}])
4;; => ({:order/paid? true})

Then make the result concrete if needed:

1(into []
2      (filter :order/paid?)
3      [{:order/paid? true}
4       {:order/paid? false}
5       {:order/id 3}])
6;; => [{:order/paid? true}]

REPL practice should answer specific questions:

Question REPL habit
What does this return? Evaluate the smallest form
Is this lazy or concrete? Check the printed result and type when needed
Did this mutate the original? Evaluate the original value again
Is this a good function shape? Try two or three representative inputs

Review Checklist

Before moving on, make sure you can:

  • read a map, vector, set, list, symbol, and keyword without translating them to Java first
  • explain why map returns a sequence and mapv returns a vector
  • use let to name intermediate values
  • update maps with assoc, update, assoc-in, and update-in
  • use -> or ->> to clarify a pipeline
  • keep side effects at visible boundaries

Quiz: Clojure Syntax Practice

### What does `update` return when used on a map? - [x] A new map with the transformed value. - [ ] The same map mutated in place. - [ ] A Java bean. - [ ] A lazy sequence of keys. > **Explanation:** Clojure maps are immutable. `update` returns a new map and leaves the original value unchanged. ### Why does `->>` fit many collection pipelines? - [x] It threads the prior result into the last argument position. - [ ] It mutates the first collection. - [ ] It imports Java streams. - [ ] It disables laziness. > **Explanation:** Sequence functions usually take the collection last, so `->>` makes the data flow read top to bottom. ### What is a good use for `let`? - [x] Naming intermediate values in a calculation. - [ ] Reassigning a mutable local variable repeatedly. - [ ] Importing a namespace. - [ ] Creating a Java class. > **Explanation:** `let` introduces lexical names for values; it does not create Java-style mutable slots. ### Which function updates a nested value? - [x] `update-in` - [ ] `defn` - [ ] `println` - [ ] `quote` > **Explanation:** `update-in` applies a function at a nested path and returns a new data structure. ### True or False: The REPL is useful for checking the value of small forms before writing larger functions. - [x] True - [ ] False > **Explanation:** REPL-first development helps build syntax fluency by showing exactly what forms evaluate to.
Revised on Saturday, May 23, 2026