ClojureScript - Differences from Clojure
ClojureScript
Differences from Clojure
Differences from Clojure
Table of Contents
Rationale
State and Identity
Dynamic Development
Functional Programming
Lisp
Runtime Polymorphism
Concurrent Programming
Hosted on the JVM
Getting Started
The Reader
The REPL and main
Evaluation
Special Forms
Macros
Other Functions
Data Structures
Seqs
Protocols
Metadata
Namespaces
Libs
Vars and the Global Environment
Refs and Transactions
Agents
Atoms
Host Interop
Hinting
Compilation and Class Generation
Other Libraries
Contributing
What follows is a section-by-section review of sections on the left-hand navigation panels of
and
, enumerating what is different in ClojureScript and in some cases the similarities.
Rationale
The rationale for ClojureScript is much the same as for Clojure, with JavaScript in the role of platform, and additional emphasis on the reach of JS, as it is obviously not as rich a platform.
deeper discussion on ClojureScript’s rationale
can be found elsewhere on this site.
State and Identity
Same as Clojure. Clojure’s identity model is simpler and more robust than mutable state, even in single threaded environments.
Dynamic Development
As with Clojure, ClojureScript supports REPL-driven development, providing easily-launched REPLs for various JavaScript environments. See
Quick Start
for details.
Additionally, ClojureScript’s self-hosting capability supports extending the dynamic nature to
pure JavaScript environments where third-party REPLs and other dynamic facilities can be created.
Functional Programming
ClojureScript has the same immutable persistent collections as Clojure on the JVM.
Lisp
Unlike in Clojure, ClojureScript macro definitions and their use cannot be intermixed in the same compilation stage. See the Macros section below.
Runtime Polymorphism
ClojureScript protocols have the same semantics as Clojure protocols.
Concurrent Programming
Clojure’s model of values, state, identity, and time is valuable even in single-threaded environments.
Atoms work as in Clojure
No Refs nor STM
The user experience of
binding
is similar to that in Clojure
Vars
not reified at runtime
many development time uses of reification are obviated by access to Clojure data structures via the analyzer
def
produces ordinary JS variables
Agents are currently not implemented
Hosted on the JVM
ClojureScript is hosted on JavaScript VMs
Optionally, it may use Google’s Closure compiler for optimization
It is
designed to leverage Google’s Closure library
, and participates in its dependency/require/provide mechanism
Getting Started
See
Quick Start
The Reader
Numbers
ClojureScript currently only supports integer and floating point literals that map to JavaScript primitives
Ratio, BigDecimal, and BigInteger literals are currently not supported
Equality on numbers works like JavaScript, not Clojure:
(= 0.0 0) ⇒ true
Characters
ClojureScript does not have character literals. Instead characters are the same as in JavaScript (i.e. single-character strings)
Lists, Vectors, Maps, and Set literals are the same as in Clojure
Macro characters
Because there is no character type in ClojureScript,
produces a single-character string.
read
The
read
and
read-string
functions are located in the
cljs.reader
namespace
Tagged Literals
The same as
Clojure Tagged Literals
, except that reader functions used at the compilation stage are similar to a Clojurescript macros, in that they
should return a Clojurescript code form (or a literal such as a string or number).
The Clojure compiler does not automatically require reader functions referred to in data_readers.clj/c,
but the Clojurescript compiler does.
see
The Reader
for more details
The REPL and main
See the
Quick Start
for ClojureScript REPL usage.
The standard ClojureScript REPLs support the Clojure main pattern.
Evaluation
ClojureScript has the same evaluation rules as Clojure
load
exists, but only as a REPL special function
load-file
exists, but only as a REPL special function
While Clojure performs locals clearing, ClojureScript does not
Special Forms
The following ClojureScript special forms are identical to their Clojure
cousins:
if
do
let
letfn
quote
loop
recur
throw
, and
try
var
notes
Vars are not reified at runtime. When the compiler encounters the
var
special form it emits a
Var
instance reflecting
compile time
metadata. (This satisfies many common
static
use cases.)
def
notes
def
produces ordinary JS variables
:private
metadata is not enforced by the compiler
Private var access triggers an analysis warning
:const
metadata:
will cause inlining of compile-time static EDN values
causes
case
test constants which are symbols resolving to
^:const
Vars to be inlined with their values
def
form evaluates to the value of the init form (instead of the var), unless the
:def-emits-var
compiler option is set (which defaults to
true
for REPLs)
if
notes
the section about Java’s boolean boxes is irrelevant in ClojureScript
fn
notes
There is currently no runtime enforcement of arity when calling a fn
monitor-enter
monitor-exit
, and
locking
are not implemented
Macros
ClojureScript’s macros must be defined in a different
compilation stage
than the one from
where they are consumed. One way to achieve this is to define them in one namespace and use them from another.
Macros are referenced via the
:require-macros
keyword in
namespace declarations:
(ns my.namespace
(:require-macros [my.macros :as my]))
Sugared and other
ns
variants can be employed in lieu of using the
:require-macros
primitive; see Namespaces below for details.
Macros are written in
*.clj
or
*.cljc
files and are compiled either as Clojure when
using regular ClojureScript or as ClojureScript when using bootstrapped / self-host
ClojureScript. One point of note is that the code generated by Clojure-based
ClojureScript macros must target the capabilities in ClojureScript.
ClojureScript namespaces
can
require macros from the selfsame namespace, so long as they
are kept in different compilation stages. So, for example a
foo.cljs
or
foo.cljc
file can make
use of a
foo.cljc
or
foo.clj
file for its macros.
Unlike in Clojure, in ClojureScript a macro and a function can have the same name (for example the
cljs.core/+
macro and
cljs.core/+
function can coexist).
You may be wondering: “If that’s the case, which one do I get?” ClojureScript (unlike Clojure) has two distinct stages that make use of two separate non-interacting namespaces. Macroexpansion occurs first, so a form like
(+ 1 1)
initially involves the
cljs.core/+
macro. On the other hand, in a form like
(reduce + [1 1])
, the
symbol is not in operator position, and passes untouched through macroexpansion to analysis/compilation where it is resolved as the
cljs.core/+
function.
Other Functions
printing
*out*
and
*err*
is currently not implemented
regex support
ClojureScript
regular expression support is that of JavaScript
asserts
In JVM ClojureScript it is not possible to dynamically set
*assert*
to false at runtime. Instead the
:elide-asserts
compiler option must be used to effect elision. (On the other hand, in self-hosted ClojureScript
*assert*
behaves identically to Clojure.)
Data Structures
nil
While in Clojure,
nil
is identical to Java’s
null
, in ClojureScript
nil
is equivalent to JavaScript’s
null
and
undefined
Numbers
Currently ClojureScript numbers are just JavaScript numbers
Coercions are not implemented, since there are currently no types to coerce to
Characters
JavaScript has no character type. Clojure characters are represented internally as single-character strings
Keywords
ClojureScript keywords are not guaranteed to be
identical?
, for fast equality testing use
keyword-identical?
Collections
Persistent collections available
Ports of Clojure’s implementations
Transient support in place for persistent vectors, hash maps and hash sets
Most but not all collection fns are implemented
StructMaps
ClojureScript does not implement
defstruct
create-struct
struct-map
struct
, or
accessor
Seqs
Seqs have the same semantics as in Clojure, and almost all Seq library functions are available in ClojureScript.
Protocols
defprotocol
and
deftype
extend-type
extend-protocol
work as in Clojure
Protocols are not reified as in Clojure, there are no runtime protocol objects
Some reflective capabilities (
satisfies?
) work as in Clojure
satisfies?
is a macro and must be passed a protocol name
extend
is not currently implemented
specify
, extend immutable values to protocols - instance level
extend-type
without wrappers
Metadata
Works as in Clojure.
Namespaces
Namespaces in ClojureScript are compiled to Google Closure namespaces which are represented as nested JavaScript objects. Importantly this means that namespaces and vars have the potential to clash - however the compiler can detect these problematic cases and will emit a warning when this occurs.
You must currently use the
ns
form only with the following caveats
You must use the
:only
form of
:use
:require
supports
:as
:refer
, and
:rename
:refer :all
not supported
all options can be skipped
in this case a symbol can be used as a libspec directly
that is,
(:require lib.foo)
and
(:require [lib.foo])
are both supported and mean the same thing
:rename
specifies a map from referred var names to different symbols (and can be used to prevent clashes)
prefix lists
are not supported
The only options for
:refer-clojure
are
:exclude
and
:rename
:import
is available only for importing Google Closure classes
ClojureScript types and records should be brought in with
:use
or
:require :refer
, not
:import
ed
Macros must be defined in a different
compilation stage
than the one from
where they are consumed. One way to achieve this is to define them in one namespace and use them from another. They are referenced via the
:require-macros
:use-macros
options to
ns
:require-macros
and
:use-macros
support the same forms that
:require
and
:use
do
Implicit macro loading
: If a namespace is required or used, and that namespace itself requires or uses macros from its own namespace, then the macros will be implicitly required or used using the same specifications. Furthermore, in this case, macro vars may be included in a
:refer
or
:only
spec. This oftentimes leads to simplified library usage, such that the consuming namespace need not be concerned about explicitly distinguishing between whether certain vars are functions or macros. For example:
(ns testme.core (:require [cljs.test :as test :refer [test-var deftest]]))
will result in
test/is
resolving properly, along with the
test-var
function and the deftest macro being available unqualified.
Inline macro specification
: As a convenience,
:require
can be given either
:include-macros true
or
:refer-macros
[syms…​]
. Both desugar into forms which explicitly load the matching Clojure file containing macros. (This works independently of whether the namespace being required internally requires or uses its own macros.) For example:
(ns testme.core
(:require [foo.core :as foo :refer [foo-fn] :include-macros true]
[woz.core :as woz :refer [woz-fn] :refer-macros [apple jax]]))
is sugar for
(ns testme.core
(:require [foo.core :as foo :refer [foo-fn]]
[woz.core :as woz :refer [woz-fn]])
(:require-macros [foo.core :as foo]
[woz.core :as woz :refer [apple jax]]))
Auto-aliasing clojure namespaces
: If a non-existing
clojure.*
namespace is required or used and a matching
cljs.*
namespace exists, the
cljs.*
namespace will be loaded and an alias will be automatically established from the
clojure.*
namespace to the
cljs.*
namespace. For example:
(ns testme.core (:require [clojure.test]))
will be automatically converted to
(ns testme.core (:require [cljs.test :as clojure.test]))
Libs
Existing Clojure libs will have to conform to the ClojureScript subset in order to work in ClojureScript.
Additionally, macros in Clojure libs must be compilable as ClojureScript in order to be consumable in
self-host / bootstrapped ClojureScript via its
cljs.js/*load-fn*
capability.
Vars and the Global Environment
def
and
binding
work as in Clojure
but on ordinary js variables
Clojure can represent unbound vars. In ClojureScript
(def x)
results in
(nil? x)
being
true
. (In this case,
(identical? nil x)
is
false
, but
(identical? js/undefined x)
is
true
.)
In Clojure,
def
yields the
var itself
. In ClojureScript
def
yields the
value
, unless the REPL option
:def-emits-var
is set (this defaults to
true
for REPLs).
Atoms work as in Clojure
Refs and Agents are not currently implemented
Validators work as in Clojure
intern
not implemented - no reified Vars
Refs and Transactions
Refs and transactions are not currently supported.
Agents
Agents are not currently supported.
Atoms
Atoms work as in Clojure.
Host Interop
The host language interop features (
new
, etc.) work as in Clojure where possible, e.g.:
goog/LOCALE
=> "en"

(let [sb (goog.string.StringBuffer. "hello, ")]
(.append sb "world")
(.toString sb))
=> "hello, world"
In ClojureScript
Foo/bar
always means that
Foo
is a namespace. It cannot be used for the Java static field access pattern common in Clojure as there’s no reflection information in JavaScript to determine this.
The special namespace
js
provides access to global properties:
js/Infinity
=> Infinity
To access object properties (including functions that you want as a value, rather than to execute) use a leading hyphen:
(.-NEGATIVE_INFINITY js/Number)
=> -Infinity
Hinting
While
^long
and
^double
—when used on function parameters—are type
declarations
in Clojure, they are type
hints
in ClojureScript.
Type hinting is primarily used to avoid reflection in Clojure. In ClojureScript, the only type hint of significance is the
^boolean
type hint: It is used to avoid checked
if
evaluation (which copes with the fact that, for example,
and
""
are false in JavaScript and true in ClojureScript).
Compilation and Class Generation
Compilation is different from Clojure:
All ClojureScript programs are compiled into (optionally optimized) JavaScript.
Individual files can be compiled into individual JS files for analysis of output
Production compilation is whole-program compilation via Google Closure compiler
gen-class
gen-interface
, etc. are unnecessary and unimplemented in ClojureScript
Other Libraries
ClojureScript currently includes the following non-core namespaces ported from Clojure:
clojure.set
clojure.string
clojure.walk
clojure.zip
clojure.data
clojure.core.reducers
fold
is currently an alias for
reduce
cljs.pprint
(port of
clojure.pprint
cljs.spec
(port of
clojure.spec
cljs.test
(port of
clojure.test
Contributing
Clojure and ClojureScript share the same
Contributor Agreement and development process
Rationale
Google Closure
Documentation
Overview
Reference
Tools
Guides
Community
Resources
Contributing
Companies
Events
Code
API Cheatsheet
Source
Changelog
ETC
ClojureTV
Legal
License
Copyright Rich Hickey |
Published 2026-01-23
Site design by Tom Hickey