Turing.jl: a probabilistic programming language that sits inside Julia's scientific stack
Bayesian inference with probabilistic programming.
At a glance
- What is it?
- Turing.jl is a general-purpose Bayesian inference package written in Julia, with models declared through the @model macro and sampled with NUTS. It is grant-funded research software that states it prioritises correctness and stability over feature coverage, which is the right frame for deciding whether to adopt it.
- Who is it for?
- Adopt Turing.jl if your model can be expressed in Julia and you want the posterior to live next to the rest of your numerical code, and if you are willing to read the API reference rather than expect a guided path. Do not adopt it if you need a large catalogue of prebuilt model families, a point-and-click interface, or a support contract; the README describes grant-funded research software that prioritises correctness and stability over broad feature coverage.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly Julia, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem Turing.jl solves, and who actually has that problem
Bayesian inference stops being a statistics exercise and becomes a software problem the moment a model has more than a handful of parameters. You need a way to write a joint distribution, a way to get gradients through it, and a sampler that explores the posterior without hand-tuning step sizes. Doing that by hand in a general-purpose language means writing the log-density, differentiating it, and debugging the sampler before you have answered any question about your data. Turing.jl exists to remove that layer. The README describes it as a general-purpose probabilistic programming package for Bayesian and likelihood-based inference, implemented in Julia and designed to interoperate with the language's scientific computing ecosystem. The audience is therefore narrower than a general statistics library: it is people already writing numerical code in Julia who want the model to live in the same process as the rest of their analysis, rather than exporting arrays to a separate inference tool. If your model is a handful of closed-form conjugate updates, you do not need this. If your model is a hierarchical structure with partial pooling, or a differential equation with unknown parameters, the manual approach stops scaling and the package starts earning its place.
How a @model becomes a posterior: the mechanism visible in the docs
The central object is the model macro, which the README attributes to DynamicPPL.jl. Inside a @model block you write the generative story directly: priors on the left of the tilde, the likelihood on the right, with the linear predictor computed in ordinary Julia in between. The README's example places Normal(0, 1) priors on an intercept and slope, a truncated(Cauchy(0, 3); lower=0) prior on the residual scale, and an MvNormal likelihood over the observations. That block is not a specification file in a separate syntax. It is Julia code that captures a log-density, and the package exposes that log-density and its gradient as an interface for customised inference. Conditioning is the other half of the mechanism. The README writes it as `posterior = linear_regression(x) | (; y = y)`, which takes the model and supplies the observed data, producing a conditioned object that the sampler can consume. Sampling is then a separate call: `sample(rng, posterior, NUTS(), 1000)` draws 1,000 posterior samples with the No-U-Turn sampler. The explicit rng argument is worth noting, because it means the randomness is something you pass in and can control, not global state. For gradient-based algorithms the package needs automatic differentiation, and the README names ForwardDiff.jl and Mooncake.jl as the preferred backends, with others such as Enzyme.jl available through DifferentiationInterface.jl. That backend is a real decision point, not an implementation detail: it determines whether your model's log-density is differentiable in a way the sampler can use.
Getting it running: the commands and the version floor
The installation path in the README is two steps. Install Julia 1.10.8 or later from the official Julia website, then in a REPL run `using Pkg; Pkg.add("Turing")`. There is no separate build system, no Python environment, and no compiled binary to fetch. The README's worked example is short enough to reproduce from the text: load Random and Turing, create `rng = Xoshiro(1)`, define the linear regression model, build a covariate vector with `x = range(-1, 1; length=20)`, generate synthetic observations with `y = 1 .+ 2 .* x .+ 0.2 .* randn(rng, length(x))`, condition with `posterior = linear_regression(x) | (; y = y)`, and sample with `chain = sample(rng, posterior, NUTS(), 1000)`. The version floor matters more than it looks. Julia 1.10.8 or later is a specific patch release, not a major version, which suggests the package tracks Julia internals closely enough that patch-level differences are considered relevant. If your production environment is pinned to an older Julia, that is the first thing to check, and it is checkable in one command before you write a single model. The README also points to HISTORY.md for changes and to the GitHub releases page for published versions, which is where you would look to understand what moved between releases.
Where Turing.jl is the wrong tool
The README's own project scope section is the most useful limitation statement in the material. It says Turing is grant-funded research software that prioritises correctness and stability over broad feature coverage, and that new features are often developed through research projects or collaborations. Read that as a boundary rather than a disclaimer. It means the package is not trying to be a catalogue of ready-made model families, and it means the roadmap is shaped by research funding and collaboration rather than by a commercial support organisation. Two practical consequences follow. First, if your problem is a standard model that another tool ships as a one-line call, you are paying a modelling cost here that you would not pay elsewhere. Second, if something in documented functionality gives an incorrect result, the README says reproducible reports of incorrect results or unexpected failures in documented functionality guide further work, which is an honest description of how prioritisation happens but also an admission that undocumented edge cases are not the priority. There is a governance detail that reinforces this: the contributing section asks that proposed features be discussed in an issue before implementation, reserves reviewer privileges for sustained, substantive contributors and people invited by a team member, and directs breaking changes to a `breaking` branch while non-breaking changes target `main`. That is a healthy process for a research project, and it is also a process where an outside team's urgent feature request is not the thing driving the queue.
The alternative, and the actual difference in approach
The closest comparison available in the material is not another package but another modelling syntax inside the same ecosystem. The README states that models can be written with @model, in BUGS syntax via JuliaBUGS.jl, or graphically with DoodlePPL. That is a genuinely different trade-off, and it is worth being precise about it. The @model route is code-first: you write Julia expressions, you control the linear predictor explicitly, and you can drop into the log-density and gradient interface when the built-in algorithms are not what you want. The BUGS route is declaration-first: the model is described in a syntax that predates Julia and that a large body of published epidemiological and ecological models already uses, which matters if you are reimplementing a model from a paper rather than designing a new one. The graphical route via DoodlePPL is a third position, where the structure is expressed as a graph rather than as text. These are not competing products; they are three front ends that the same project points at. The real question is which one lets you express your model without fighting the syntax. If your model has a loop over groups with a non-centred parameterisation, the @model form is probably shorter. If your model is a published BUGS listing, retyping it into @model form introduces transcription errors for no benefit. The README does not claim the three are equivalent in capability, and you should not assume they are.
Maintenance cost, release cadence and the MIT licence
Turing.jl is MIT licensed, which is permissive and imposes no copyleft obligation on your own code. That is the easy part. The maintenance picture is more interesting. The three most recent releases listed are v0.48.0 on 2026-09-04, v0.47.4 on 2026-09-02, and v0.47.3 on 2026-09-01. Three releases in four days, with a minor version bump in the middle of them, tells you the project is under active development and that the version number is not a stability promise in the way a long-lived 1.x line would be. For a team adopting this, the practical implication is that you should pin the version in your Julia environment rather than tracking the latest, and you should read HISTORY.md before bumping, because a 0.47 to 0.48 jump is where interface changes would land. The README's own framing reinforces this: it calls the software grant-funded research software, which is a statement about where maintenance effort comes from. Grant funding is not the same as a vendor SLA. If your organisation needs someone contractually accountable for a bug fix, that is a different procurement conversation, and the contributing section's note that reviewer privileges are reserved for sustained contributors suggests the maintainer group is deliberately small. Budget for the possibility that you are the one writing the reproduction case. The README explicitly says reproducible reports of incorrect results guide further work, so a minimal reproducer is the currency here.
What to verify before you commit a model to it
Start with the environment, not the model. Confirm that your Julia installation is at 1.10.8 or later, because that is the floor the README states. Then reproduce the README's linear regression example verbatim, including the Xoshiro seed, on the machine you intend to use. That tells you the AD backend resolves and that NUTS runs at all in your setup, which is a different question from whether it runs on your model. Next, decide your differentiation backend deliberately. The README names ForwardDiff.jl and Mooncake.jl as the preferred backends for gradient-based algorithms, with Enzyme.jl and others available through DifferentiationInterface.jl. Which one you need depends on the operations inside your model, and this is the single most likely place for a model that looks correct on paper to fail at runtime. Then write a small version of your actual model, not a toy, and check that it compiles and samples before you invest in the full version. Finally, decide how you will pin the package. Given the release cadence visible in the version list, an unpinned dependency on Turing in a long-running analysis is a reproducibility hazard, and the fix is a pinned environment rather than a policy. The README's citation section names the ACM Transactions on Probabilistic Machine Learning paper from 2025 and the 2018 AISTATS paper, which is where to look if you need to describe the method rather than the software in a write-up.
Editorial conclusion
Adopt Turing.jl if your model can be expressed in Julia and you want the posterior to live next to the rest of your numerical code, and if you are willing to read the API reference rather than expect a guided path. Do not adopt it if you need a large catalogue of prebuilt model families, a point-and-click interface, or a support contract; the README describes grant-funded research software that prioritises correctness and stability over broad feature coverage. Before committing, verify three things on your own machine: that your model compiles and samples under the pinned Julia version, which AD backend you need (ForwardDiff.jl and Mooncake.jl are the documented preferences, with Enzyme.jl reachable through DifferentiationInterface.jl), and how you will pin the package given that v0.48.0, v0.47.4 and v0.47.3 shipped within the same week.
Community notes