Coding · Programming languages
Julia tutorial for beginners
Short answer
Julia is a high-level, dynamically typed language designed for fast numerical and scientific computing. It reads like Python or MATLAB but compiles each function to native code the first time it is called, so loops can run at speeds close to C. Its central idea is multiple dispatch: a function picks its method based on the types of all its arguments. Install it with juliaup and run a file with julia hello.jl.
What Julia is and who uses it
Julia was created by Jeff Bezanson, Stefan Karpinski, Viral Shah and Alan Edelman, and it became public in 2012. Version 1.0 arrived in 2018, and the 1.x series keeps backward compatibility. Julia aims to solve the "two-language problem": prototyping in a slow, easy language and then rewriting the hot parts in C or Fortran.
Julia is used by scientists, engineers and data analysts for simulation, differential equations, optimization, statistics, machine learning and high-performance computing. Its package ecosystem covers data frames, plotting, GPU computing and automatic differentiation.
Install Julia and run Hello, world
The recommended installer is juliaup, which installs and updates Julia versions on Windows, macOS and Linux (see julialang.org/downloads). After installation, julia starts the REPL and julia file.jl runs a script.
# hello.jl
println("Hello, world!")julia hello.jl
# Hello, world!
julia
# julia> 1 + 2
# 3In the REPL, press ] to enter package mode (for example add DataFrames), ? for help on a name, and ; for a shell. Press Backspace to return to the normal prompt.
Variables and basic types
Variables need no declaration, but every value has a concrete type, which you can inspect with typeof. You can add type annotations to function arguments and struct fields when you want them.
| Type | Example | Notes |
|---|---|---|
| Int64 | 42 | Default integer on 64-bit systems; overflow wraps around |
| Float64 | 3.14, 1e-3 | Default floating point |
| Rational | 1//3 | Exact fraction |
| String | "hello" | UTF-8; interpolate with "$name" |
| Char | 'a' | A single Unicode character |
| Bool | true, false | Logical values |
| Symbol | :name | An interned name, often used as a key or option |
| Nothing | nothing | "No value", returned by functions with no result |
name = "Ana"
age = 30
println("$name is $age, next year $(age + 1)")
# Ana is 30, next year 31
typeof(age) # Int64
7 / 2 # 3.5 (always a float)
7 ÷ 2 # 3 (integer division, also div(7, 2))
2^10 # 1024Julia accepts Unicode names such as π or α; in the REPL and most editors you type \pi and press Tab to get π.
Control flow
Blocks end with the keyword end; indentation is for readability only. if, for and while work as in most languages, and comprehensions build arrays in one line.
function sign_name(n)
if n > 0
"positive"
elseif n < 0
"negative"
else
"zero"
end
end
for i in 1:3
println("$i squared is $(i^2)")
end
squares = [x^2 for x in 1:5 if isodd(x)] # [1, 9, 25]
x = 5
result = x > 3 ? "big" : "small" # "big"1:3 is a range that includes both ends. The last expression in a function or if block is its value, so sign_name(-2) returns "negative" without an explicit return.
Functions
add(x, y) = x + y # one-line definition
function greet(name = "world"; punctuation = "!")
return "Hello, $name$punctuation"
end
greet() # "Hello, world!"
greet("Ana", punctuation = ".") # "Hello, Ana."
square = x -> x^2 # anonymous function
map(square, [1, 2, 3]) # [1, 4, 9]
v = [3, 1, 2]
sort!(v) # v is now [1, 2, 3]Positional arguments come before the semicolon and keyword arguments after it. By convention, a function whose name ends in ! modifies one of its arguments, such as sort! or push!, while sort returns a new array.
Arrays, tuples and dictionaries
| Structure | Literal | Notes |
|---|---|---|
| Vector | [1, 2, 3] | 1-based, mutable, grows with push! |
| Matrix | [1 2; 3 4] | Spaces separate columns, ; separates rows |
| Tuple | (1, "a") | Immutable, fixed length |
| Named tuple | (name = "Ana", age = 30) | Fields accessed with .name |
| Dict | Dict("a" => 1) | Mutable hash map |
| Set | Set([1, 2, 2]) | Unique values |
v = [10, 20, 30]
v[1] # 10 (first element)
v[end] # 30
push!(v, 40)
A = [1 2; 3 4]
A * A # matrix product: [7 10; 15 22]
A .* A # element-wise: [1 4; 9 16]
v .+ 1 # broadcasting: [11, 21, 31, 41]
sqrt.([1, 4, 9]) # [1.0, 2.0, 3.0]The dot syntax is called broadcasting: f.(x) applies f to every element, and .+, .* work element by element. Julia fuses chains of dotted operations into a single loop.
Signature feature: multiple dispatch
A Julia function can have many methods. When you call it, Julia chooses the most specific method for the types of all the arguments. This replaces class methods: types hold data, and behavior is added by defining methods for them, even from another package.
abstract type Shape end
struct Circle <: Shape
r::Float64
end
struct Rect <: Shape
w::Float64
h::Float64
end
area(c::Circle) = π * c.r^2
area(r::Rect) = r.w * r.h
shapes = [Circle(1.0), Rect(2.0, 3.0)]
println(round.(area.(shapes), digits = 2)) # [3.14, 6.0]Because Julia compiles a specialized version of a function for the argument types it sees, the first call of a function is slower (it includes compilation) and later calls are fast. Use mutable struct when fields must change after creation.
Error handling
Julia uses exceptions. Throw a built-in exception type such as ArgumentError or DomainError, or define your own as a struct that subtypes Exception. tryparse returns nothing instead of throwing.
function parse_age(s)
n = tryparse(Int, s)
n === nothing && throw(ArgumentError("not a number: $s"))
n < 0 && throw(DomainError(n, "age must not be negative"))
return n
end
try
parse_age("abc")
catch e
if e isa ArgumentError
println("Error: ", e.msg)
else
rethrow()
end
end
# Error: not a number: abcA small complete program: word frequencies
text = "the cat and the hat and the bat"
counts = Dict{String,Int}()
for w in split(text)
counts[w] = get(counts, w, 0) + 1
end
top = sort(collect(counts), by = last, rev = true)
for (word, n) in first(top, 2)
println(word, " ", n)
end
# the 3
# and 2split breaks the string on whitespace, get returns a default of 0 for new words, collect turns the dictionary into a vector of word => count pairs, and by = last sorts by the count.
Where to go next
- The official Julia manual at docs.julialang.org, starting with "Getting Started".
- The learning page at julialang.org, which lists tutorials, books and courses.
- Read the "Performance Tips" chapter of the manual before optimizing code.
- Explore packages such as DataFrames.jl, Plots.jl and DifferentialEquations.jl.
FAQ
Is Julia faster than Python?
For code with many loops and numeric work, Julia is usually much faster than pure Python because it compiles to native code. Python with libraries such as NumPy can be fast for vectorized operations, so the difference depends on the task.
Why does Julia start counting at 1?
Julia follows the convention of mathematics and of languages such as MATLAB, R and Fortran, where the first element of an array has index 1.
Why is the first run of my Julia code slow?
Julia compiles each function for the argument types the first time it is called. Later calls reuse the compiled code, so measure performance after a warm-up call or with a benchmarking package.
Is Julia free?
Yes. Julia is open source under the MIT license, and almost all of its packages are open source too.