Julia — Reference

Source: https://docs.julialang.org/en/v1/

Julia

  • Created: development began 2009; first public release 2012-02-14 by Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and Alan Edelman (Wikipedia).
  • Latest stable: Julia 1.12.6 (docs as of 2026-04-09) (docs.julialang.org).
  • v1.0: 2018-08-08 (the “no-breaking-changes” promise begins).
  • Owner: JuliaLang open-source project; commercial steward JuliaHub, Inc. (formerly Julia Computing, founded 2015). License: MIT.
  • Paradigms: multi-paradigm; multiple dispatch is the central organizing principle; functional, imperative, and (informal) OO supported.
  • Typing: dynamic, strong, optional type annotations; rich parametric type system; types are first-class runtime values.
  • Memory: tracing, generational, parallel garbage collector (Julia 1.10+).
  • Compilation: JIT via LLVM; method specialization on argument types; AOT possible via PackageCompiler.jl (sysimages, app bundles).
  • Primary domains: scientific computing, numerical analysis, ML, differential equations, climate, finance, pharmacometrics, HPC.
  • Official docs: https://docs.julialang.org/en/v1/

At a glance

Julia attacks the “two-language problem”: prototype in a high-level language, ship in C++. It is dynamically typed but specialization-compiled per argument signature, so well-written Julia is within 2x of C for tight loops while feeling like Python or MATLAB. Multiple dispatch is everywhere — +, print, and your own functions all dispatch on the types of all arguments, enabling composability that single-dispatch OO cannot match (DifferentialEquations.jl + Measurements.jl + Unitful.jl just compose).

Getting started

Install: juliaup is the official installer/version manager (since 2022). curl -fsSL https://install.julialang.org | sh (Unix), winget install julia -s msstore (Windows). Manage with juliaup add 1.10, juliaup default lts.

Hello world:

println("Hello, world!")

Run: julia hello.jl or paste into the REPL.

REPL: launch with julia. Has four modes accessed via prefix keys:

  • julia> default
  • ? help mode (?map)
  • ; shell mode (;ls)
  • ] package mode (]add Plots)

Project layout (a package):

MyPkg/
  Project.toml         # name, uuid, deps, compat
  Manifest.toml        # exact resolved versions (lockfile)
  src/MyPkg.jl         # module entry point
  test/runtests.jl
  docs/                # Documenter.jl source

Package/build tool: built-in Pkg (using Pkg; Pkg.add("Plots") or ]add Plots). Pkg manages environments per directory via Project.toml + Manifest.toml (lockfile). Activate with ]activate .. The General registry (https://github.com/JuliaRegistries/General) hosts ~10,000+ packages.

Basics

Types and literals:

  • Int (platform-sized, usually Int64), Int8/16/32/64/128, UInt*, Float16/32/64, BigInt, BigFloat, Bool, Char (Unicode codepoint), String (UTF-8), Symbol (interned, :foo), Rational (3//4), Complex (1 + 2im).
  • Containers: Vector{T} (= Array{T,1}), Matrix{T} (= Array{T,2}), Tuple, NamedTuple ((a=1, b=2)), Dict{K,V}, Set{T}.
  • nothing (singleton, Nothing type), missing (statistical missing, Missing type), Union{T, Nothing} is the optional pattern.

Variables/scoping: dynamic typing with optional :: annotations. Lexical scoping; local, global, const modifiers. Top-level scope of a module is global; let introduces a new scope.

Control flow: if/elseif/else/end, for x in xs ... end, while ... end, break, continue. Ternary: cond ? a : b. Short-circuit: cond && action, cond || fallback. Comprehensions: [x^2 for x in 1:10 if isodd(x)]. Generators: (x^2 for x in 1:10).

Functions:

add(x, y) = x + y                  # short form
function add(x::Int, y::Int)       # method on Int,Int — multiple dispatch
    x + y
end
add(x, y; verbose=false) = ...     # keyword args after ;
sq = x -> x^2                       # anonymous
map(x -> x^2, 1:5)

Last expression is the return value; return is optional but allowed for early exit. Methods are added to generic functions — every function has potentially many methods.

Strings: UTF-8 native, immutable. Indexing is by byte offset, not character: s[1], but iteration for c in s is by character. string("a", 1, :b) for concat, * also concatenates strings ("a" * "b"). Interpolation: "x = $(x+1)". Raw strings: raw"C:\path". Triple-quoted multi-line: """...""".

Collections: 1-indexed by convention (column-major arrays, like Fortran/MATLAB). push!, pop!, append! (the ! suffix marks mutation). Broadcasting with .: sin.(xs), xs .+ ys, f.(a, b) — fuses into a single loop with no temporaries.

Intermediate

Type system depth:

  • Abstract types (abstract type Number end) form a tree; concrete types are leaves and only they can have instances.
  • Parametric types: Vector{T} where T <: Number. Type parameters are inferred or explicit.
  • Union{Int, String}, Tuple{Int, String} (covariant in tuples), Type{T} (the type representing T).
  • where clauses constrain method signatures: f(x::Vector{T}) where T <: Number.
  • Triangular dispatch: f(x::T, y::T) where T requires both args same type.

Modules: module MyMod ... end. using (brings names into scope), import (bring in qualified). Submodules via nesting. Each package is a top-level module. Re-export with export.

Error handling: throw(ErrorException("msg")), error("msg"). try ... catch e ... finally ... end. Exception types form a hierarchy under Exception. @assert cond "msg" for invariants. Idiomatic: return nothing or Union{T, Nothing} for absence rather than throwing.

Concurrency primitives:

  • Tasks (coroutines): t = @task f(); schedule(t), fetch(t). @async expr. Channel{T} for CSP-style.
  • Threads (true OS threads, set with julia -t auto or JULIA_NUM_THREADS): Threads.@threads for i in ..., Threads.@spawn. Atomics via Threads.Atomic{T}. ReentrantLock, SpinLock.
  • Distributed (multi-process): using Distributed; addprocs(4); @distributed for ...; pmap(f, xs). Per-process workers communicate by message passing.

I/O: read, write, open(f, "r") do io ... end. Printf.@printf, @sprintf. Streams: stdin, stdout, stderr. IOBuffer for in-memory. JSON via JSON.jl / JSON3.jl. CSV via CSV.jl + DataFrames.jl.

Stdlib highlights: LinearAlgebra (BLAS/LAPACK wrappers, \, eigen, svd), Statistics (mean, var, cor), Random (RNGs), Dates, Printf, Test (@test, @testset), Distributed, Serialization, Sockets, Logging, Pkg, REPL, Markdown, Profile, SharedArrays.

Advanced

Memory / GC: tracing, generational mark-sweep with parallel marking (since 1.10) and parallel sweep (since 1.11). Manual control via GC.gc(), GC.enable(false). Allocations are the #1 perf killer — @allocated, @time, and the --track-allocation=user flag identify hot spots. Stack-allocate small immutables by making them struct (immutable, value-type) instead of mutable struct (heap, reference).

Concurrency deep dive: M:N scheduler — Julia tasks (coroutines) are multiplexed across Threads.nthreads() OS threads. @spawn schedules onto any thread; @spawnat :default is the same. Channel(N) is buffered; reading blocks the task (not the thread). ReentrantLock for shared state. Threads.@threads partitions a loop statically; OhMyThreads.jl and FLoops.jl give better composition. GPU: CUDA.jl (NVIDIA), AMDGPU.jl (ROCm), Metal.jl (Apple), oneAPI.jl — all expose CuArray/ROCArray/etc. that broadcast and dispatch like Array.

FFI: ccall((:fname, "libname"), RetT, (ArgT1, ArgT2), arg1, arg2) is the primitive. @ccall fname(arg::T)::RetT is the modern macro form. No header parsing needed; types map directly. Libdl for dlopen. Two-way: pass Julia closures to C with @cfunction. PythonCall.jl / PyCall.jl for Python; RCall.jl for R; JavaCall.jl, MATLAB.jl.

Reflection: rich.

  • methods(f) lists every method.
  • methodswith(T) lists methods that take T.
  • subtypes(T), supertype(T).
  • fieldnames(T), fieldtypes(T).
  • isa(x, T), typeof(x).
  • dump(x) prints recursive structure.

Performance tools:

  • @time, @allocated, @elapsed — quick checks.
  • BenchmarkTools.jl: @btime, @benchmark — proper microbench (multiple samples, GC-aware).
  • Profile.@profile (sampling profiler) + ProfileView.jl / PProf.jl for flame graphs.
  • JET.jl — static type-instability analyzer.
  • Cthulhu.jl — interactive deep dive into type-inferred IR (@descend f(args)).

God mode

Macros: full Lisp-style on the AST. A macro receives quoted Expr objects and returns one.

macro twice(ex)
    quote
        $(esc(ex))
        $(esc(ex))
    end
end
@twice println("hi")   # prints "hi" twice

Expansion is at parse time; inspect with @macroexpand @twice .... esc() controls hygiene (escape from macro’s gensym renaming).

Generated functions: emit different code per argument-type signature.

@generated function unrolled_sum(t::NTuple{N, T}) where {N, T}
    expr = :(t[1])
    for i in 2:N
        expr = :($expr + t[$i])
    end
    expr
end

Runs at compile time per signature; the returned Expr becomes the method body.

Compiler introspection — the four levels:

  • @code_lowered f(args) — desugared AST.
  • @code_typed f(args) — after type inference; shows specialized SSA IR.
  • @code_llvm f(args) — generated LLVM IR.
  • @code_native f(args) — emitted assembly.

Multiple dispatch + invalidation: when you add a method, every cached method instance whose inference depended on “this generic was incomplete” gets invalidated and may need recompilation. SnoopCompile.jl finds invalidation chains; reducing them is essential for fast TTFX (time to first X).

Precompilation + sysimages: each package precompiles to native code on install. PackageCompiler.jl lets you bake your code (and a workload) into a custom sysimage (create_sysimage), eliminating JIT latency. create_app produces a relocatable executable; create_library a shared library callable from C.

ccall internals: ccall knows about the Julia ABI for Cint, Cdouble, Cstring, Ptr{T}, Ref{T}. Variadic via (args...,)::Cint. @ccall macro gives nicer syntax. Base.unsafe_load/unsafe_store! for raw pointer ops.

Type-stable code patterns: a function is type-stable if its return type is a function of its argument types alone. Anti-patterns: branches that return different types (x > 0 ? 1 : "neg"), reading globals not declared const, untyped fields in struct. Use @code_warntype f(args) — red Anys mean instability.

Distributed / Dagger: Distributed for explicit worker management; Dagger.jl builds a parallel task graph (delayed evaluation) that schedules across threads/workers/GPUs.

Idioms & style

  • Naming: lowercasewords for functions and modules (no separator: findnext, isvalid); CamelCase for types/modules: MyType, LinearAlgebra. SCREAMING for constants. Suffix ! on functions that mutate their first argument (push!, sort!).
  • Formatter: JuliaFormatter.jl (format(".")).
  • Linter: JET.jl (type-checker + linter), Aqua.jl (package quality), StaticLint.jl (LSP backend).
  • Idiomatic patterns:
    • Define small, generic functions; let dispatch do the work.
    • Prefer Vector{T} with concrete T over Vector{Any}.
    • Use do blocks for callbacks: open("f") do io ... end.
    • Use @views to avoid array copies on slicing.
    • Use broadcasting (.) instead of explicit loops for elementwise ops.
  • Expert review focus: type stability (@code_warntype), allocation hotspots, invalidation cost of new method definitions, const-correctness of module globals, missing eltype/length methods on custom iterables, abuse of Any-typed containers, throwing in tight loops.

Ecosystem

  • Numerical / Sci: LinearAlgebra (stdlib), DifferentialEquations.jl (state-of-art ODE/PDE/SDE — composes with autodiff and units), JuMP.jl (mathematical programming), Optim.jl, NLopt.jl, IterativeSolvers.jl.
  • ML / AI: Flux.jl (pure-Julia DL), Lux.jl (functional, explicit-state), MLJ.jl (sklearn-style frame), Transformers.jl, Turing.jl (probabilistic programming), SciML/SciMLSensitivity.jl (universal differential equations).
  • Autodiff: ForwardDiff.jl, Zygote.jl, Enzyme.jl (LLVM-level reverse-mode, fastest), ReverseDiff.jl.
  • Data: DataFrames.jl, Tables.jl (interface), CSV.jl, Arrow.jl, Parquet2.jl, Query.jl, DuckDB.jl.
  • Plotting: Plots.jl (multi-backend), Makie.jl (high-perf, GPU-capable, publication-quality), Gadfly.jl (grammar of graphics), PlotlyJS.jl.
  • Web: HTTP.jl, Genie.jl (full-stack framework), Oxygen.jl (FastAPI-like), Franklin.jl (static sites), Pluto.jl (reactive notebooks — kills Jupyter for Julia work).
  • Testing: Test (stdlib, @testset, @test), ReTest.jl (selective), Aqua.jl (anti-pattern detection), JET.jl.
  • Docs: Documenter.jl is canonical; docstrings ("""...""" above defs) are first-class.
  • Notable users: NASA (CSO models), Pfizer (Pumas pharmacometrics), Federal Reserve (DSGE models), Climate Modeling Alliance (CliMA), AstraZeneca, BlackRock (Aladdin numerics), MIT, CERN.

Gotchas

  • Time-to-first-X (TTFX): JIT compilation latency on first call is a notorious pain point. PackageCompiler.jl sysimages and the --compile=min --optimize=0 flags help; pre-1.9 it was much worse.
  • 1-indexed arrays: xs[1] is the first element. xs[end] is the last. Iterating for i in 1:length(xs) works but for i in eachindex(xs) is safer (handles offset arrays).
  • Vector{Any} is slow: [1, "a"] infers Vector{Any}. Type-annotate or use a Tuple for heterogeneous data.
  • Globals must be const to be type-stable in functions that read them.
  • Mutation in loops can de-specialize if you change a variable’s type partway through (write x = 0; for ...; x += 1.0; endx becomes Union{Int, Float64}).
  • Type piracy: defining a method on a type you don’t own and a function you don’t own. Breaks invalidation, surprises other packages.
  • @async on threaded runtime: pre-1.10, @async did not migrate across threads; use Threads.@spawn explicitly.
  • Broadcasting fusion: sin.(cos.(xs)) fuses into one loop; sin.(cos.(xs)) .+ 1 also fuses. But assigning to a temporary breaks fusion.
  • Integer overflow is silent: typemax(Int) + 1 == typemin(Int). Use Base.checked_add or BigInt for safety.
  • == vs === vs isequal: == is value equality with NaN!=NaN; === is bitwise/identity; isequal treats NaN==NaN, used by Dict/Set.
  • missing propagation: 1 + missing == missing, missing == missing == missing (not true!). Use ismissing(x).
  • Pkg environment confusion: forgetting ]activate . means installing into the default @v1.x shared env, polluting it.
  • include("file.jl") is order-dependent — there are no header files. The Revise.jl workflow is essential for not restarting Julia on every change.

Citations