Installing Julia

TLDR: Use juliaup

Head to the Julia downloads page.

  1. If you are on Windows you can find Julia (and juliaup) on the Windows store
  2. For most other platforms you can run:
curl -fsSL https://install.julialang.org | sh

Warning: Do not install Julia using your system package manager or other package managers like Homebrew. Those versions are a frequent source of bug reports.

juliaup

juliaup is the Julia version manager, which we use to manage different versions of Julia, download updates and more:

juliaup status
juliaup list

For now we are going to ensure you have the current release version of Julia using the following commands:

juliaup add release
juliaup default release

(This was probably already done on your first installation but we all want to be on the same page).

Installing other software

The Julia REPL

The Julia REPL is the primary way many users interact with Julia:

julia

While many languages like Python ship with a REPL, Julia's REPL is uniquely powerful and fully-featured.

TLDR: The Julia REPL has 4 default modes: Julia, package (]), help (?) and shell (;).

The Read-Eval-Print Loop (or REPL) is the most basic way to interact with Julia, check out its documentation for details. You can start a REPL by typing julia into a terminal, or by clicking on the Julia application in your computer. It will allow you to play around with arbitrary Julia code:

julia> a, b = 1, 2;

julia> a + b
3

This is the standard (Julia) mode of the REPL, but there are three other modes you need to know. Each mode is entered by typing a specific character after the julia> prompt. Once you're in a non-Julia mode, you stay there for every command you run. To exit it, hit backspace after the prompt and you'll get the julia> prompt back.

Help mode (?)

By pressing ? you can obtain information and metadata about Julia objects (functions, types, etc.) or unicode symbols. The query fetches the docstring of the object, which explains how to use it.

help?> println
println([io::IO], xs...)

Print (using ) xs to io followed by a newline. If io is not supplied, prints to the default output stream .

See also to add colors etc.

Examples

julia> println("Hello, world")
Hello, world

julia> io = IOBuffer();

julia> println(io, "Hello", ',', " world.")

julia> String(take!(io))
"Hello, world.\n"

If you don't know the exact name you are looking for, type a word surrounded by quotes to see in which docstrings it pops up.

Package mode (])

By pressing ] you access Pkg.jl, Julia's integrated package manager, whose documentation is an absolute must-read. Pkg.jl allows you to:

As an illustration, we download the package Example.jl inside our current environment:

(starting) pkg> add Example
β”Œ Warning: The Pkg REPL mode is intended for interactive use only, and should not be used from scripts. It is recommended to use the functional API instead.
β”” @ Pkg.REPLMode /opt/hostedtoolcache/julia/1.12.6/x64/share/julia/stdlib/v1.12/Pkg/src/REPLMode/REPLMode.jl:398
   Resolving package versions...
   Installed Example ─ v0.5.5
    Updating `~/work/QNumericsCrashCourse/QNumericsCrashCourse/starting/Project.toml`
  [7876af07] + Example v0.5.5
    Updating `~/work/QNumericsCrashCourse/QNumericsCrashCourse/starting/Manifest.toml`
  [7876af07] + Example v0.5.5
Precompiling packages...
    338.1 ms  βœ“ Example
  1 dependency successfully precompiled in 0 seconds
(starting) pkg> status
Status `~/work/QNumericsCrashCourse/QNumericsCrashCourse/starting/Project.toml`
  [7876af07] Example v0.5.5

Note that the same keywords are also available in Julia mode:

julia> using Pkg

julia> Pkg.rm("Example")
    Updating `~/work/QNumericsCrashCourse/QNumericsCrashCourse/starting/Project.toml`
  [7876af07] - Example v0.5.5
    Updating `~/work/QNumericsCrashCourse/QNumericsCrashCourse/starting/Manifest.toml`
  [7876af07] - Example v0.5.5
        Info We haven't cleaned this depot up for a bit, running Pkg.gc()...
      Active manifest files: 4 found
      Active artifact files: 4 found
      Active scratchspaces: 0 found
     Deleted no artifacts, repos, packages or scratchspaces

The package mode itself also has a help mode, accessed with ?, in case you're lost among all these new keywords.

Shell mode (;)

By pressing ; you enter a terminal, where you can execute any command you want. Here's an example for Unix systems:

shell> ls ./writing
ls: cannot access './writing': No such file or directory

Diving into Julia

Basic Arithmetic

julia> 1 + 2

julia> 5 / 10

julia> sqrt(100)

Julia also has easy access to special Unicode characters, which includes a lot of common LaTeX math symbols:

julia> √2

You can access these symbols and many more wwith autocomplete:

julia> Ξ£ # type \Sigma and press Tab

Variables

Variables are assigned with the = sign. Variable names can be almost arbitrary Unicode:

julia> Ξ£ = +
+

julia> Ξ£(1,2,3)
6

Julia variables are essentially just names, they can be re-bound to arbitrary values like functions, types, numbers, etc.

julia> Ξ£ = 3
3

julia> Ξ£ + Ξ£
6

julia> πŸ₯±, πŸ™„, 🀣 = 3.6, false, true
(3.6, false, true)

julia> πŸ₯± + πŸ™„ + 🀣
4.6

You can look up how to type any strange characters by pasting them into the help REPL:

help?> πŸ™„
"πŸ™„" can be typed by \:face_with_rolling_eyes:<tab>

Strings

Strings are concatenated with * (not +), repeated with ^, and support interpolation with $:

julia> foo = "The eye rolling emoji has value $(πŸ™„)"
The eye rolling emoji has value false

julia> bar = " which is equal to $(3 - 2 > 10)"
which is equal to false

julia> foo * bar
The eye rolling emoji has value false which is equal to false

julia> "ha"^3
hahaha

The standard library ships with all the string-mangling helpers you'd expect:

julia> s = "  Hello, Julia World!  "
Hello, Julia World!

julia> length(s)
23

julia> uppercase(s)
HELLO, JULIA WORLD!

julia> lowercase(s)
hello, julia world!

julia> strip(s)                          # trim whitespace
Hello, Julia World!

julia> strip("--hi--", '-')              # trim a particular character
hi

julia> startswith(s, "  Hello")
true

julia> endswith(strip(s), "!")
true

julia> occursin("Julia", s)
true

julia> replace(s, "Julia" => "Python")
Hello, Python World!

julia> split("a,b,,c", ",")              # split on a delimiter
SubString{String}["a", "b", "", "c"]

julia> join(["x", "y", "z"], "-")        # inverse of split
x-y-z

julia> # `string` is the generic stringifier β€” accepts anything `show`-able

julia> string(1, " + ", 2, " = ", 1 + 2)
1 + 2 = 3

Printing

You've already seen println in passing. Here are the printing tools you'll reach for most often:

julia> print("no newline... ")           # write to stdout, no newline
no newline...

julia> println("and now a newline")      # newline at the end
and now a newline

julia> # Interpolation is the most common way to format output

julia> x, y = 3, 4
(3, 4)

julia> println("$x + $y = $(x + y)")
3 + 4 = 7

julia> # `@show` is invaluable for quick debugging: it prints `expr = value`

julia> @show x + y;
x + y = 7

julia> # `printstyled` lets you color or emphasize text in the terminal

julia> printstyled("careful!\n"; color=:yellow, bold=true)
careful!

There are two slightly different "render this value" functions:

julia> print("hello"); println()
hello

julia> show("hello");  println()
"hello"

Arithmetic and Comparison Operators

Op Notes
+, -, * the usual
/ division, always returns a float
Γ· (\div) integer (truncating) division
% or rem remainder
^ exponentiation, 2^10 not 2**10
==, != (or β‰ ) (in)equality
<, >, <= (≀), >= (β‰₯) comparisons
=== identity, same object in memory

Updating Ops

julia> x = 0
0

julia> x += Ο€
3.141592653589793

julia> x -= 2
1.1415926535897931

julia> x *= 3
3.4247779607693793

julia> x /= 4
0.8561944901923448

These operators desugar into:

julia> x = x + Ο€ # x += Ο€
3.997787143782138

Complicated Equations

Julia has a nice juxtoposition syntax:

julia> x = 10
10

julia> 3x^2 + 4x + 5
345

Bools and Conditionals

true and false have their own type, Bool, you cannot just use the integers 0 or 1 in a conditional statement.

julia> typeof(true)
Bool

julia> true + true
2

if/elseif/else blocks are expressions, the last value is returned:

julia> x = 5
5

julia> y = if x > 0
    "positive"
elseif x < 0
    "negative"
else
    "zero"
end
positive

Ternary form for short conditions:

julia> x > 0 ? "pos" : "nonpos"
pos

Short-circuit && and || are common in idiomatic Julia:

julia> x > 0 && println("yes")
yes

julia> x < 0 || println("nope")
nope

Functions

Long form, with explicit return:

julia> function add(x, y)
    return x + y
end;

julia> add(1, 2)
3

return is optional β€” the last expression is returned automatically β€” but it's needed for early exits:

julia> function clamp_positive(x)
    x < 0 && return 0
    x
end;

julia> clamp_positive(-3), clamp_positive(4)
(0, 4)

Short (assignment) form:

julia> mul(x, y) = x * y;

julia> mul(3, 4)
12

Anonymous form, useful for one-off callbacks:

julia> (x -> x^2)(5)
25

You can bind an anonymous function to a name like any other value:

julia> f = (x, y) -> x + y^2;

julia> f(3, 4)
19

Basics of Types

TLDR: Every value has a specific concrete type.

We're going to talk about types more later but for now:

julia> typeof(3)
Int64

julia> x = 3
3

julia> typeof(x)
Int64

julia> typeof(1.5)
Float64

julia> typeof(3 + 4im)
Complex{Int64}

julia> typeof("string cheese!")
String

julia> typeof((fooplus(x, y) = x + y))
ERROR: syntax: invalid keyword argument name "fooplus(x, y)" around string:1
Stacktrace:

Containers

Tuples

julia> mytuple = ("Purple", 3.14159, "People", typeof, "Eater")
("Purple", 3.14159, "People", typeof, "Eater")

julia> mytuple[1] # Julia is 1-based!!! (Kinda)
Purple

julia> mytuple[begin]
Purple

julia> mytuple[end]
Eater

julia> typeof(mytuple)
Tuple{String, Float64, String, typeof(typeof), String}

julia> a, b, c = mytuple # unpack a tuple
("Purple", 3.14159, "People", typeof, "Eater")

julia> a, b... = mytuple # unpack the remainder into b
("Purple", 3.14159, "People", typeof, "Eater")

julia> for e in mytuple
    println(e)
end
Purple
3.14159
People
typeof
Eater

Tuples are covariant in their field types, so Tuple{Int, Float64} <: Tuple{Real, Real}.

Named Tuples

Like tuples but with named fields:

julia> mynt = (name="Purple", value=3.14159, fn=typeof)
(name = "Purple", value = 3.14159, fn = typeof)

julia> mynt.name # access by name
Purple

julia> mynt[1] # or by position
Purple

julia> mynt[end]
typeof

julia> typeof(mynt)
@NamedTuple{name::String, value::Float64, fn::typeof(typeof)}

julia> a, b, c = mynt # unpack positionally
(name = "Purple", value = 3.14159, fn = typeof)

julia> for v in mynt
    println(v)
end
Purple
3.14159
typeof

Named tuples are also covariant.

Vectors

Vectors use the [ ] literal:

julia> v = [1, 2, 3]
[1, 2, 3]

julia> v[1]
1

julia> v[begin], v[end]
(1, 3)

julia> v[1] = 99; v
[99, 2, 3]

julia> push!(v, 4); v
[99, 2, 3, 4]

julia> typeof(v) # Vector{Int64}
Vector{Int64}

The literal infers T from the contents β€” mix types and you fall back to Any:

julia> typeof([1, "two", 3.0])
Vector{Any}

Ranges

Ranges are lazy iterables; collect materializes them as a Vector:

julia> r = 1:5
1:5

julia> r[1], r[end]
(1, 5)

julia> collect(r)
[1, 2, 3, 4, 5]

julia> 0:0.25:1
0.0:0.25:1.0

julia> typeof(r)
UnitRange{Int64}

Dictionaries

julia> d = Dict("a" => 1, "b" => 2)
Dict("b" => 2, "a" => 1)

julia> d["a"]
1

julia> d["c"] = 3; d
Dict("c" => 3, "b" => 2, "a" => 1)

julia> haskey(d, "a")
true

julia> for (k, v) in d
    println("$k => $v")
end
c => 3
b => 2
a => 1

julia> typeof(d) # Dict{String, Int64}
Dict{String, Int64}

Mixed key/value types collapse to Any:

julia> typeof(Dict(:x => 1, "y" => 2.0))
Dict{Any, Real}

Loops

for iterates over anything iterable (ranges, vectors, ...):

julia> for i in 1:3
    println(i)
end
1
2
3

in can also be written ∈ (\in<tab>):

julia> for i ∈ 1:4
    println("I love the number $i")
end
I love the number 1
I love the number 2
I love the number 3
I love the number 4

Warning: Unfortunately for various reasons plain scalar numbers are iterable. So watch out!

while runs until the condition fails:

julia> n = 3
3

julia> while n > 0
    println(n)
    n -= 1
end
3
2
1

Working with Vectors

The Vectors section above showed push! and the literal [ ] form. Here are the operations that show up in almost every script:

Mutating (the trailing ! is convention for "modifies its argument"):

julia> v = [3, 1, 4, 1, 5]
[3, 1, 4, 1, 5]

julia> push!(v, 9)            # add to the end
[3, 1, 4, 1, 5, 9]

julia> pop!(v)                # remove and return the last element
9

julia> pushfirst!(v, 0)       # add to the front
[0, 3, 1, 4, 1, 5]

julia> popfirst!(v)
0

julia> append!(v, [2, 6])     # extend with another collection
[3, 1, 4, 1, 5, 2, 6]

julia> v
[3, 1, 4, 1, 5, 2, 6]

Aggregates β€” these work on anything iterable, not just vectors:

julia> length(v), sum(v), prod(v)
(7, 22, 720)

julia> maximum(v), minimum(v), extrema(v)
(6, 1, (1, 6))

julia> sum(1:100)             # works on ranges too
5050

julia> sum(x^2 for x in 1:10) # generator: no intermediate vector
385

Slicing uses ranges β€” including the begin/end keywords:

julia> v = [10, 20, 30, 40, 50, 60]
[10, 20, 30, 40, 50, 60]

julia> v[2:4]                 # elements 2..4
[20, 30, 40]

julia> v[1:2:end]             # every other element
[10, 30, 50]

julia> v[end-2:end]           # last three
[40, 50, 60]

Searching and predicates β€” these take a function (or a Fix2 like >(3)):

julia> v = [3, 1, 4, 1, 5, 9, 2, 6]
[3, 1, 4, 1, 5, 9, 2, 6]

julia> findfirst(==(5), v)    # index of the first 5
5

julia> findall(>(3), v)       # all indices where the element is > 3
[3, 5, 6, 8]

julia> any(iseven, v)
true

julia> all(>(0), v)
true

julia> count(iseven, v)
3

Transformations:

julia> map(x -> x^2, v)
[9, 1, 16, 1, 25, 81, 4, 36]

julia> filter(iseven, v)
[4, 2, 6]

julia> sort(v)
[1, 1, 2, 3, 4, 5, 6, 9]

julia> sort(v; rev=true)
[9, 6, 5, 4, 3, 2, 1, 1]

julia> reverse(v)
[6, 2, 9, 5, 1, 4, 1, 3]

Comprehensions are a compact way to build vectors (or matrices, see below):

julia> [x^2 for x in 1:5]
[1, 4, 9, 16, 25]

julia> [x for x in 1:10 if iseven(x)]    # with filter
[2, 4, 6, 8, 10]

julia> [(i, j) for i in 1:3, j in 1:3]   # nested β†’ matrix
[(1, 1) (1, 2) (1, 3); (2, 1) (2, 2) (2, 3); (3, 1) (3, 2) (3, 3)]

Matrices

A matrix literal uses spaces between columns and ; between rows:

julia> A = [1 2 3
     4 5 6
     7 8 10]
[1 2 3; 4 5 6; 7 8 10]

julia> size(A)                # (rows, cols)
(3, 3)

julia> size(A, 1)             # just the row count
3

julia> ndims(A), eltype(A)
(2, Int64)

Indexing extends from vectors with a second axis. : means "all of this dimension":

julia> A[1, 2]                # row 1, column 2
2

julia> A[:, 2]                # all of column 2
[2, 5, 8]

julia> A[1, :]                # all of row 1
[1, 2, 3]

julia> A[1:2, 2:3]            # 2Γ—2 submatrix
[2 3; 5 6]

Constructors you'll use constantly:

julia> zeros(2, 3)
[0.0 0.0 0.0; 0.0 0.0 0.0]

julia> ones(3, 3)
[1.0 1.0 1.0; 1.0 1.0 1.0; 1.0 1.0 1.0]

julia> rand(2, 4)             # uniform on [0, 1)
[0.6549915943444159 0.05068523046329321 0.9213027318090715 0.2022613451070443; 0.7495060814279565 0.3687752906197864 0.840510284153138 0.9593642228247093]

julia> randn(2, 4)            # standard normal
[-0.4415523735340024 -0.5829655980312182 0.6941926594750597 0.08000753528839065; -0.3017402533636737 0.926296672500494 0.852935060872784 -0.510954439620447]

Linear algebra is built in. * is real matrix multiplication, ' is the (conjugate) transpose, and \ solves linear systems:

julia> v = [1, 2, 3]
[1, 2, 3]

julia> A * v                  # matrix–vector
[14, 32, 53]

julia> A * A                  # matrix–matrix
[30 36 45; 66 81 102; 109 134 169]

julia> A'                     # transpose
[1 4 7; 2 5 8; 3 6 10]

julia> A \ [1, 2, 3]          # solve A x = b
[-0.33333333333333337, 0.6666666666666666, 3.172065784643303e-17]

sum, maximum, etc. accept dims to reduce along a particular axis:

julia> sum(A)                 # one scalar
46

julia> sum(A; dims=1)         # 1Γ—3, column sums
[12 15 19]

julia> sum(A; dims=2)         # 3Γ—1, row sums
[6; 15; 25;;]

Broadcasting

If you put a . before an operator or after a function name, Julia applies it element by element. This is one of the most useful pieces of syntax in the language:

julia> v = [1, 2, 3, 4]
[1, 2, 3, 4]

julia> v .+ 10                # add 10 to every element
[11, 12, 13, 14]

julia> v .* v                 # elementwise square
[1, 4, 9, 16]

julia> v .^ 2
[1, 4, 9, 16]

julia> sin.(v)                # apply sin to each element
[0.8414709848078965, 0.9092974268256817, 0.1411200080598672, -0.7568024953079282]

julia> sqrt.(1:5)
[1.0, 1.4142135623730951, 1.7320508075688772, 2.0, 2.23606797749979]

It works for any function, including ones you just defined:

julia> greet(name) = "hi $name";

julia> greet.(["alice", "bob", "carol"])
["hi alice", "hi bob", "hi carol"]

The @. macro saves you from sprinkling dots everywhere β€” every operator and call in the expression becomes broadcasting:

julia> x = [1.0, 2.0, 3.0];

julia> @. x^2 + 2x + 1
[4.0, 9.0, 16.0]

Broadcasting also combines arrays of different shapes by aligning their dimensions. A row vector and a column vector give you the "outer" combination:

julia> row = [10 20 30]       # 1Γ—3
[10 20 30]

julia> col = [1, 2, 3]        # length-3 column
[1, 2, 3]

julia> row .+ col             # 3Γ—3 outer sum
[11 21 31; 12 22 32; 13 23 33]

Putting it all together

Time to combine functions, containers, broadcasting and printing into something useful. None of the functions below have type annotations β€” they're duck-typed, so they'll work on whatever you throw at them as long as the operations inside make sense.

Fibonacci

julia> function fib(n)
    a, b = 0, 1
    for _ in 1:n
        a, b = b, a + b
    end
    a
end;

julia> fib.(0:10)             # broadcast over a range
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

The recursive one-liner is much slower but also much prettier:

julia> fib_rec(n) = n < 2 ? n : fib_rec(n - 1) + fib_rec(n - 2);

julia> fib_rec.(0:10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

Quadratic formula

julia> function quadratic(a, b, c)
    s = sqrt(b^2 - 4a*c)
    (-b + s) / (2a), (-b - s) / (2a)
end;

julia> quadratic(1, -3, 2)                # roots of xΒ² βˆ’ 3x + 2
(2.0, 1.0)

Because we never told Julia what a, b, c are, complex coefficients work for free β€” and that includes finding complex roots of a real polynomial if you pass a complex input:

julia> quadratic(1, 2 + 0im, 5)           # complex roots of xΒ² + 2x + 5
(-1.0 + 2.0im, -1.0 - 2.0im)

Mean and variance on anything iterable

julia> mean(xs) = sum(xs) / length(xs);

julia> variance(xs) = (m = mean(xs); sum((x - m)^2 for x in xs) / (length(xs) - 1));

julia> mean(1:10)
5.5

julia> mean([1.0, 2.0, 3.0])
2.0

julia> mean((4, 5, 6, 7))                 # a tuple
5.5

julia> variance(1:10)
9.166666666666666

Newton's method

A classic example of a higher-order function β€” one that takes other functions as arguments:

julia> function newton(f, fβ€², x0; tol=1e-10, maxiter=100)
    x = x0
    for _ in 1:maxiter
        Ξ” = f(x) / fβ€²(x)
        x -= Ξ”
        abs(Ξ”) < tol && return x
    end
    x
end;

julia> # √2 as a root of xΒ² βˆ’ 2

julia> newton(x -> x^2 - 2, x -> 2x, 1.0)
1.4142135623730951

julia> # Same code finds a complex root of xΒ² + 1

julia> newton(x -> x^2 + 1, x -> 2x, 1.0 + 1.0im)
8.463737877036721e-23 + 1.0im

Estimating Ο€ with Monte Carlo

A handful of broadcasts and a sum give us a Monte Carlo estimate of Ο€ by sampling points in the unit square and checking how many fall inside the unit quarter-disk:

julia> function estimate_Ο€(n)
    xs = rand(n)
    ys = rand(n)
    inside = sum(xs.^2 .+ ys.^2 .<= 1)
    4 * inside / n
end;

julia> estimate_Ο€(100_000)
3.1456

Word frequencies

Strings, dictionaries, loops, and the get(d, k, default) pattern all in one place:

julia> function word_counts(text)
    counts = Dict{String, Int}()
    for word in split(text)
        clean = lowercase(strip(word, ['.', ',', '!', '?', ';', ':']))
        isempty(clean) && continue
        counts[clean] = get(counts, clean, 0) + 1
    end
    counts
end;

julia> word_counts("To be, or not to be: that is the question. To live, or to die!")
Dict("question" => 1, "live" => 1, "or" => 2, "that" => 1, "not" => 1, "is" => 1, "the" => 1, "die" => 1, "to" => 4, "be" => 2)
CC BY-SA 4.0 Raye Kimmerer. Last modified: June 22, 2026.
Website built with Franklin.jl and the Julia programming language.