Julia Syntax

Julia's surface syntax is deliberately close to what mathematicians write and to what Python and MATLAB programmers already recognise. Where it differs, the difference carries meaning: 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\_1x₁Subscripted names
x\hatx̂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, functionssnake_casemean_squared_error
Functions that mutatetrailing !sort!(v)
Types, modulesCamelCaseDataFrame, LinearAlgebra
Constants, globalsSCREAMING_SNAKEMAX_ITER
Predicatesprefix is / hasisempty, haskey
Not exportedleading __validate(x)
Type parameterscapital letter{T}, {S, T}
Abstract types (interfaces)prefix AbstractAbstractVector

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
42Int64Default integer on a 64-bit machine
0xff, 0b1010, 0o17UInt8/IntHex, binary, octal
1_000_000Int64Underscores are readability only
3.14, 1e-9, 2.0f0Float64, Float32f0 forces single precision
1//3Rational{Int64}Exact fraction — no rounding
2imComplex{Int64}Imaginary unit; 3 + 4im
'A', '\n'CharSingle quotes; a Unicode code point
Inf, NaNFloat64Floating-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 / elseConditional branchingif x > 0 ... end
forIterate a collection or rangefor i in 1:10 ... end
whileLoop while a condition holdswhile n > 0 ... end
break / continueExit the loop / skip one iterationcontinue
returnReturn a value from a functionreturn x + 1
functionDefine a named or anonymous functionfunction f(x) ... end
doPass a block as the first argumentmap(x) do v ... end
try / catch / finallyHandle exceptionstry ... catch e ... end
letIntroduce a local scopelet x = 1 ... end
beginGroup statements into one blockbegin a; b end
endClose any blockfunction ... end
isa / inType test / membership testx 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
structImmutable composite typestruct Point x::Int end
mutable structComposite type with writable fieldsmutable struct Cell v end
abstract typeDeclare a node in the type hierarchyabstract type Shape end
primitive typeDeclare a bit-level typeprimitive type MyInt 16 end
whereConstrain type parametersf(x::T) where {T <: Number}
moduleCreate a namespacemodule Geometry ... end
baremoduleModule without Base importedbaremodule Kernel ... end
usingImport names, allow extensionusing Statistics
importImport names, require qualificationimport Base: show
exportMark names as public for usingexport area, perimeter
constBind a name that may not be reassignedconst G = 9.81
global / localForce a scope explicitlyglobal counter += 1
true / falseThe two Bool literalsx = 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
macroDefine a code-rewriting constructmacro timed(ex) ... end
quoteReturn an expression without evaluating itquote 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 collectionssqrt.([1, 4, 9])
eval, escFunctions, not keywords: evaluate / escape hygieneeval(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.