Skip to content
AgileLogics
6 min read

Best Practices for Production Julia

Prerequisites: Package development and deployment

A checklist of habits that make Julia code fast, correct, and easy to maintain.

Design

  • Put code in functions and packages, not in global scripts.
  • Design around multiple dispatch: small generic functions, abstract types for families of types, methods for special cases.
  • Don't over-type arguments. Use ::Real or ::AbstractVector only to choose methods or document intent. Over-specific types break composability (units, dual numbers, GPU arrays).
  • Avoid type piracy: only extend functions for types you own.
  • Return nothing for "no result" and throw exceptions for real errors.

Performance

  • Keep functions type-stable; test critical ones with @inferred.
  • Give struct fields concrete or parametric types.
  • No untyped globals in hot code — pass arguments or use const.
  • Preallocate and use in-place ! functions, views, and fused broadcasts in loops.
  • Benchmark with BenchmarkTools and profile before optimizing.

Reliability

  • One project environment per project; commit Manifest.toml for applications.
  • Tests in test/runtests.jl, run in CI on the Julia versions you support (the current release and the LTS).
  • Seed random number generators in tests and simulations with explicit Xoshiro(seed) objects.
  • Use the Logging standard library (@info, @warn, @error, @debug) instead of println for diagnostics.

Security

  • Never eval or Meta.parse untrusted input — it runs arbitrary code.
  • Build shell commands with backtick syntax (`ls $dir`) — Julia passes each interpolated value as a separate argument, with no shell injection.
  • Keep dependencies up to date (] update) and review what you add.

Putting it together

using Logging, Statistics

"""
    summarize(xs::AbstractVector{<:Real}) -> NamedTuple

Return the count, mean, and standard deviation of `xs`,
skipping `NaN` values.
"""
function summarize(xs::AbstractVector{<:Real})
    clean = filter(!isnan, xs)
    if length(clean) < length(xs)
        @warn "skipped NaN values" skipped = length(xs) - length(clean)
    end
    isempty(clean) && throw(ArgumentError("no valid values"))
    return (n = length(clean), mean = mean(clean), std = length(clean) > 1 ? std(clean) : 0.0)
end

logger = ConsoleLogger(stdout)     # log to stdout instead of stderr
with_logger(logger) do
    println(summarize([1.0, 2.0, 3.0, 4.0]))
    println(summarize([2, 4, NaN]))
end
try
    summarize(Float64[])
catch err
    println(sprint(showerror, err))
end
(n = 4, mean = 2.5, std = 1.2909944487358056)
┌ Warning: skipped NaN values
│   skipped = 1
└ @ Main C:\demo\main.jl:12
(n = 2, mean = 3.0, std = 1.4142135623730951)
ArgumentError: no valid values

A docstring, a generic signature, structured logging, clear errors, and a type-stable named-tuple result — the habits that make Julia code reliable.

What's next

Try the Advanced Projects, then read What You Should Know Now — and What's Next for where to go from here.