close
Skip to content
Steffan edited this page Mar 11, 2026 · 5 revisions

Basic Tips

  • % can break whitespace after numbers, like #(+ 1%). This also means you can use % as a variable name to save on whitespace: (dotimes[%\d](prn(+ 1%)))

  • N and M suffixes on numeric literals create BigInt and BigDecimal values, respectively. Clojure BigInt is distinct from Java BigInteger, but you can convert with the biginteger fn.

  • The return value of def can be derefed, e.g. @(def x 1).

  • recur also works in fns, including #( ).

  • Babashka provides these namespace aliases:

    • clojure.set as set
    • clojure.string as str
  • Many JDK classes and methods are available. See Babashka's classes.clj for an exact list.

I/O

  • Use prn instead of println for numbers and symbols.
  • doto can be useful for printing complicated intermediate expressions.
  • Often you can use a quoted symbol instead of a string, e.g. "Fizz" => 'Fizz.
  • A Lisp formatter implementation is available as clojure.pprint/cl-format. It can be useful in some situations, despite the long name.

Reference

Looping

There are many of ways to loop in Clojure. Here's some advice on when to use what:

  • To loop a fixed number of times for side effects, use dotimes.
    • If you're using a constant, a character literal can be used instead of an integer, which sometimes saves bytes and/or chars.
  • To loop over a sequence for side effects, use doseq.
    • Use a single doseq for nested loops over multiple sequences.
    • run! can be situationally occasionally useful too.
  • To produce a sequence, then use map/filter/keep/etc.
    • for can be situationally useful too.
  • To loop with one state variable, use nth and iterate.
  • To loop over a sequence with one state variable, use reduce.
    • Sometimes you can save by using the two-argument version.
  • Otherwise, use recursion
    • Ideally an anonymous short function with recur. This requires tail recursion and doesn't allow nesting with other short functions.
    • Even a non-short function is better than loop.

Ironically, the loop macro is never optimal.

Handling Arguments

(mapv #(println %)*command-line-args*)
(doseq[a *command-line-args*](println a))

Usually mapv is best, but doseq is better when you have other nested short fns.

Indexing

There're a few different ways to get a value from a sequence:

(ds idx)               ; Shortest, but only works for vectors, sets, and maps; no default and errors when out of range
(ds idx default?)      ; Has default, but only works for maps
(get ds idx default?)  ; Nil/default when out of range, but only for indexed sequences
(nth ds idx)           ; Works for all sequences, even lazy ones

Conditionals

(if cond x y)          ; Short circuits, you can leave the false section out to return nil, needs a truthy or (falsey/nil) condition
(and/or cond x)        ; Short circuits, multiple conditions
(case n v x default)   ; Short circuits, checks specific values, has default
({v x}n default)       ; Same as case, but no short circuit
([x y]n)               ; Needs a numeric 0/1 condition
(get[x y]n default)    ; 0/1 condition or non-short-circuiting default

Syntax-quoting

Syntax-quoting can be used instead of sequence fns in a variety of cases:

(concat a b)
; vs
`(~@a~@b)

(cons a b)
; vs
`(~a~@b)

All sequences can be spliced inside syntax-quoting, including strings, sets, maps, and nil.

Vector and set literals can also be used:

(conj s a)
; vs
`#{~@s~a}
; vs
`[~@s~a]

Map literals work too, but require an even number of forms inside.

Clone this wiki locally