Julia Syntax
end closes every block, whitespace is insignificant, semicolons are optional, and identifiers may contain Unicode letters and symbols. Learn these rules once and the rest of the language reads cleanly.
This page is the grammar reference for the whole track. It describes how a statement ends, how blocks are delimited, how to write comments and documentation, which characters may form a name, how literals are written, and finally the complete list of reserved keywords with a short note on each.
Syntax Characteristics
Julia is a language of expressions. Almost everything you write produces a value: an if block yields the branch it took, a loop yields nothing, a function definition yields the function. Understanding that everything-is-an-expression explains syntax choices that look arbitrary in other languages.
Statement Endings & Blocks
A newline ends a statement. There is no required terminator — no semicolon, no period, no end on simple lines. Semicolons exist, but their job is to separate multiple statements on one line or to suppress output at the REPL.
x = 10 # newline ends the statement
y = 20; z = 30 # semicolon separates two statements on one line
x + y + z; # trailing semicolon suppresses the printed result
Blocks are delimited by keywords, not by braces or indentation. A block opens with a construct keyword and closes with end. Indentation is a readability convention only — the parser ignores it, so misaligned but otherwise valid code still runs.
if x > 5
println("indentation ignored, still valid") # legal, but bad style
end
function area(w, h)
return w * h # conventionally indented by four spaces
end
Unicode Identifiers & Operators
Julia source is UTF-8, and Unicode characters are legal in identifiers. That is not decoration: it lets code look like the mathematics it implements. Greek letters, subscripts, primes, and mathematical operators all have a defined role, and the REPL offers a Tab expansion for each one so you never need a special keyboard.
| Type this then Tab | Becomes | Typical use |
|---|---|---|
\alpha | α | Named coefficients |
\beta | β | Regression parameters |
\pi | π | Built-in constant, also pi |
\theta | θ | Angles, model parameters |
\to | → | Mapping notation in comments |
\le, \ge | ≤, ≥ | Same as <= and >= |
x\_1 | x₁ | Subscripted names |
x\hat | x̂ | Estimated values |
# Unicode names are ordinary identifiers to the compiler.
α = 0.05
θ = π / 4
r̂ = 1.2
isapprox(cos(θ), sin(θ)) # true — π/4 is the crossover point
α ≤ 0.05 ? "significant" : "not" # ≤ is exactly <=
Two cautions. A Unicode name and its ASCII spelling are different identifiers — α and alpha are unrelated variables. And in shared code, Greek names should carry their conventional mathematical meaning; reinventing them as decoration makes code harder to read, not easier.
Comments & Documentation
Line & Block Comments
A single # starts a comment that runs to the end of the line. Nested block comments use #= and =# — nesting matters, because it lets you comment out a region of code that already contains a comment.
# This whole line is a comment.
rate = 0.07 # an inline comment after code is fine
#= A block comment can span
several lines. It also nests, so the inner
comment below does not terminate it early:
#= inner =#
still inside the outer comment.
=#
println(rate) # prints: 0.07
Nesting is the reason to prefer #= =# over the older trick of prefixing every line with #: disabling a block that already contains a block comment works the first time.
Docstrings
Documentation is not a comment. A string literal placed on the line before a definition becomes a docstring attached to that object, retrievable at the REPL with ? or @doc. Markdown works inside it, including code fences and cross-references.
"""
hypotenuse(a, b)
Return the length of the hypotenuse of a right triangle with legs `a` and `b`.
# Examples
```julia
hypotenuse(3, 4) # 5.0
```
"""
hypotenuse(a, b) = sqrt(a^2 + b^2)
# In the REPL:
# help> hypotenuse
Identifiers & Naming
An identifier is any name you introduce: a variable, a function, a type, a module. The rules are permissive but they encode a community convention so strong that deviating from it makes your code look foreign to other Julia programmers.
Naming Conventions
The convention table below is not enforced by the compiler — only by reviewers and style tooling — but it is nearly universal in the package ecosystem, so following it means your code reads like the standard library.
| Kind | Convention | Example |
|---|---|---|
| Variables, functions | snake_case | mean_squared_error |
| Functions that mutate | trailing ! | sort!(v) |
| Types, modules | CamelCase | DataFrame, LinearAlgebra |
| Constants, globals | SCREAMING_SNAKE | MAX_ITER |
| Predicates | prefix is / has | isempty, haskey |
| Not exported | leading _ | _validate(x) |
| Type parameters | capital letter | {T}, {S, T} |
| Abstract types (interfaces) | prefix Abstract | AbstractVector |
The trailing ! is the most useful convention in the language. By law of the community, a function ending in ! is allowed to modify one or more of its arguments in place. So sort(v) returns a new sorted array and leaves v untouched, while sort!(v) reorders v itself. Reading the name tells you which one you called.
v = [3, 1, 2]
w = sort(v) # v is unchanged, w is the sorted copy
sort!(v) # v is now [1, 2, 3] — the bang warned you
isempty(v) # false — predicate naming
typeof(v) # Vector{Int64}
Reserved Words & Escapes
Identifiers may contain letters (including Unicode letters), digits, underscore, and !, but may not start with a digit. The keywords listed in the reference section below cannot be used as names at all — if an external library forces you to name something end or function, the escape hatch is the var"..." syntax, which quotes the name as a string.
# Legal
_delta1 = 0.5
Ω = 1.2
x′ = 3.0 # prime, via x\prime then Tab
# Illegal — `end` is a keyword:
# end = 5 # ERROR: syntax: unexpected "="
# Escape hatch: a quoted name is just a string, so keywords are allowed.
var"end" = 5
println(var"end") # 5
Literals at a Glance
A literal is a value written directly in source. Julia's literals are worth a section of their own because the language has more numeric kinds than most: exact rationals and complex numbers are literals, not library types.
Numeric Literals
| Literal | Type | Note |
|---|---|---|
42 | Int64 | Default integer on a 64-bit machine |
0xff, 0b1010, 0o17 | UInt8/Int | Hex, binary, octal |
1_000_000 | Int64 | Underscores are readability only |
3.14, 1e-9, 2.0f0 | Float64, Float32 | f0 forces single precision |
1//3 | Rational{Int64} | Exact fraction — no rounding |
2im | Complex{Int64} | Imaginary unit; 3 + 4im |
'A', '\n' | Char | Single quotes; a Unicode code point |
Inf, NaN | Float64 | Floating-point infinity and not-a-number |
typeof(42) # Int64
typeof(3.14) # Float64
typeof(1//3) # Rational{Int64}
1//3 + 1//6 # 1//2 — exact arithmetic, not 0.5 with error
(3 + 4im) * 2 # 6 + 8im
Int('A') # 65 — a Char is convertible to its code point
The distinction that trips people up: integer literals are Int64, but integer division is not. 4 / 2 yields 2.0, a Float64. Use div(4, 2) for floor division or 4 // 2 for an exact rational.
String Literals
Double quotes create a String, which is a UTF-8 byte sequence, not an array of characters. Interpolation with $ inserts a value into the string without concatenation; triple quotes allow multi-line text with the indentation stripped.
name = "Julia"
"Hello, $name" # interpolation → "Hello, Julia"
"2 + 2 = $(2 + 2)" # expressions need parentheses → "2 + 2 = 4"
multiline = """
first line
second line
""" # leading indentation is removed
raw"$name is not interpolated" # raw string suffix: $ is literal text
Keyword Reference
Julia has a small keyword set — around thirty words — and every one of them is reserved: you cannot use any as an identifier. The tables below group them by role. Recognising which group a word belongs to tells you what kind of construct you are about to read.
Control & Function Keywords
These keywords shape execution order or define callable units. Nothing here is an operator: they are all leading words that open a block, with the exception of the flow-control one-liners.
| Keyword | Purpose | Example fragment |
|---|---|---|
if / elseif / else | Conditional branching | if x > 0 ... end |
for | Iterate a collection or range | for i in 1:10 ... end |
while | Loop while a condition holds | while n > 0 ... end |
break / continue | Exit the loop / skip one iteration | continue |
return | Return a value from a function | return x + 1 |
function | Define a named or anonymous function | function f(x) ... end |
do | Pass a block as the first argument | map(x) do v ... end |
try / catch / finally | Handle exceptions | try ... catch e ... end |
let | Introduce a local scope | let x = 1 ... end |
begin | Group statements into one block | begin a; b end |
end | Close any block | function ... end |
isa / in | Type test / membership test | x isa Number, 3 in 1:5 |
Type & Module Keywords
This group defines the objects that make up a program's structure. If you have used a class-based language, these are the keywords that replace class, interface, and package — and Julia's set is both smaller and more explicit.
| Keyword | Purpose | Example fragment |
|---|---|---|
struct | Immutable composite type | struct Point x::Int end |
mutable struct | Composite type with writable fields | mutable struct Cell v end |
abstract type | Declare a node in the type hierarchy | abstract type Shape end |
primitive type | Declare a bit-level type | primitive type MyInt 16 end |
where | Constrain type parameters | f(x::T) where {T <: Number} |
module | Create a namespace | module Geometry ... end |
baremodule | Module without Base imported | baremodule Kernel ... end |
using | Import names, allow extension | using Statistics |
import | Import names, require qualification | import Base: show |
export | Mark names as public for using | export area, perimeter |
const | Bind a name that may not be reassigned | const G = 9.81 |
global / local | Force a scope explicitly | global counter += 1 |
true / false | The two Bool literals | x = true |
abstract type Shape end
struct Circle <: Shape
radius::Float64
end
struct Square <: Shape
side::Float64
end
# `where` constrains a type parameter to a subtree of the hierarchy.
total_area(shapes::Vector{T}) where {T <: Shape} = sum(area, shapes)
Note how <: appears twice with two different meanings: in a type declaration it means "subtype of", and in a where clause it constrains a parameter. Both read naturally as "is a kind of".
Metaprogramming Keywords
Julia code is represented by ordinary Julia data structures, so macros are a first-class part of the syntax rather than a preprocessor. These keywords are the ones you will meet when reading library code that rewrites code — the subject of Metaprogramming & Macros.
| Keyword / symbol | Purpose | Example fragment |
|---|---|---|
macro | Define a code-rewriting construct | macro timed(ex) ... end |
quote | Return an expression without evaluating it | quote x + 1 end |
:( ) | Quoting operator (shorthand for quote) | ex = :(x + 1) |
$ | Interpolate a value into a quoted expression | :(2 * $n) |
@ | Invoke a macro | @time f() |
. (dot) | Broadcast a function over collections | sqrt.([1, 4, 9]) |
eval, esc | Functions, not keywords: evaluate / escape hygiene | eval(ex) |
Common Pitfalls
Unnecessary Semicolons
Programmers arriving from C, Java, or JavaScript tend to end every line with ;. Julia accepts it, but the semicolon means something different here: it separates statements and suppresses output. Sprinkling it at line ends is noise that occasionally hides a bug.
x = 10; # legal but pointless — the newline already ended it
println(x) # prints: 10
# The semicolon is meaningful when you DO want two statements on one line,
# or when you want the REPL to stay quiet:
a = 1; b = 2 # two statements, one line
big = rand(1000, 1000); # no output printed for this expression
Style rule: never end a line with a semicolon. Use it only as a separator, or deliberately to suppress a large result at the REPL.
Case Sensitivity
Julia is case-sensitive everywhere: variables, functions, types, module names, and even keyword-adjacent constructs. total, Total, and TOTAL are three unrelated names, and the same is true of every Unicode identifier.
total = 5
# Total → ERROR: UndefVarError: `Total` not defined
# The convention makes the error avoidable: snake_case for values,
# CamelCase for types. Use the wrong case on a type and it looks like a value.
struct Vector2D
x::Float64
y::Float64
end
v = Vector2D(1.0, 2.0) # the constructor matches the type exactly
# vector2d(1.0, 2.0) # ❌ no such name
The most common real-world instance is a package name: using dataframes fails, using DataFrames works, and the error message names the wrong casing rather than explaining the convention. Match the spelling on the package's documentation page exactly.
You now have the grammar: newline-terminated statements, end-closed blocks, nested comments, docstrings, naming conventions with the meaningful !, four flavours of literal, and the reserved-word map. Next: Variables & Constants puts these rules to work binding values to names.