LmCast :: Stay tuned in

Julia 1.13 Highlights

Recorded: Sept. 13, 2026, 8 p.m.

Original Summarized

Julia 1.13 Highlights

Download

Docs

Learn

Blog

Community

Contribute

JSoC

Star

Sponsor

Julia 1.13 Highlights

10 September 2026 |



The Julia contributors

Julia version 1.13 has been released. We want to thank all the contributors to this release and all the testers who helped find regressions and issues in the pre-releases. Without you, this release would not have been possible.
The full list of changes can be found in the NEWS file, but here we'll give a more in-depth overview of some of the release highlights.
Latency (TTFX) improvementsREPL improvementsSyntax highlightingNew fzf-style history searchBracketed paste on Windows@__FUNCTION__Hashing changesFaster GC by skipping image objects during markingScheduler and interrupt fixesIntrospection with type annotationsTracing top-level evaluation with --trace-evalJuliaC/trimPkgChange in default compression algorithm from gzip to zstdPerformance improvementsRegistries for packages tracked in the manifestRecursively collect sourcespkg> add now tries to add the same version as already-loaded packagesPkg.test no longer defaults to enabling strict bounds checkingJuliaup GUI
Latency (TTFX) improvements
Ian Butterworth, many others
Julia 1.13 takes roughly 30% less time to precompile packages than 1.12, and roughly 10-20% less time than 1.10 (LTS) depending on the machine.
Time To First X (TTFX), the time from starting Julia to getting a first result, is made up of three main costs: precompiling packages, loading them, and running the code. With the help of the community-submitted workflows at Julia-TTFX-Snippets, we have started measuring these costs more systematically on real-world examples and optimizing Julia against them.
The chart below shows the geometric mean across all 39 currently submitted workflows, on two machines. Precompilation is the fastest of 2 runs; load and execution times are the fastest of 3 runs.

This monitoring is now also part of Julia's own development process: new TTFX CI jobs run on relevant pull requests and on every commit to master, and the results are tracked at perf.julialang.org/ttfx. That tracking went live on September 7, 2026; measurements before then were ad hoc.
Julia 1.13 startup is also ~20% faster than 1.12.
% hyperfine --warmup 3 --runs 20 -N \
--command-name "julia 1.12" "julia +1.12 --startup-file=no -e ''" \
--command-name "julia 1.13" "julia +1.13 --startup-file=no -e ''"
Benchmark 1: julia 1.12
Time (mean ± σ): 69.1 ms ± 1.0 ms [User: 50.1 ms, System: 18.1 ms]
Range (min … max): 68.0 ms … 72.6 ms 20 runs

Benchmark 2: julia 1.13
Time (mean ± σ): 56.7 ms ± 0.5 ms [User: 49.1 ms, System: 18.9 ms]
Range (min … max): 56.0 ms … 58.1 ms 20 runs

Summary
julia 1.13 ran
1.22 ± 0.02 times faster than julia 1.12
REPL improvements
Syntax highlighting
Timothy, Kristoffer Carlsson
The Julia REPL now has syntax highlighting (without having to load an external package like OhMyREPL.jl):

By default, the color scheme is quite conservative, but it is easy to customize (see the documentation for the REPL). As an example, here is the same code but using the Monokai color scheme:

New fzf-style history search
Timothy
The history search (entered by default via Ctrl-R) has been redesigned and now works similarly to the command-line fuzzy finder fzf:

Among other things, the new history search has support for:

Fuzzy searching in the history.

Showing what REPL mode was used for the command.

Selecting multiple search results to put into the prompt buffer.

Syntax highlighting of the code, matching the REPL itself.

Enter the history search and type ? to see the full help.
Bracketed paste on Windows
Bracketed paste allows an application running in a terminal to know when text is being pasted (as opposed to just being typed). This can allow for more efficient and correct processing of the text being pasted. This functionality has been enabled on Linux and macOS for a long time but is now also finally available on Windows. As a concrete example, the videos below show the behavior of pasting a ~500-line function into the Julia REPL before and after enabling bracketed paste on Windows.
Before:

After:

@__FUNCTION__
Miles Cranmer, Jeff Bezanson
Like the existing @__MODULE__ and @__FILE__ macros, the new @__FUNCTION__ macro references the innermost containing function even if that function is anonymous. This should work in all kinds of functions, and is public API, unlike the internal variable #self#.
julia> fact = n -> n <= 1 ? 1 : n * @__FUNCTION__()(n - 1);

julia> fact(5)
120
Hashing changes
Andy Dienes, Jameson Nash
The hash function has been replaced. The byte-hashing algorithm is now RapidhashNano. This hash is used by default for AbstractString and many numeric types like BigInt, Rational, and large Real or Integer values. It is also much easier now for custom types to opt in to the generic implementations without having to first convert to a supported type (like String). This change offers several advantages compared to the pre-existing implementation based on MurmurHash3. It has significantly better performance, is a streaming hash so it no longer requires the length of the input up front, and has moved from C to pure Julia for better readability and maintainability.
To demonstrate the performance improvement on long strings:
using BenchmarkTools, Downloads

io = IOBuffer()
Downloads.download("https://www.gutenberg.org/cache/epub/1080/pg1080.txt", io)
s = String(take!(io));

# 1.12
@btime hash($s)
8.555 μs (0 allocations: 0 bytes)
0x5fbd2717019846ea

# 1.13
@btime hash($s)
1.742 μs (0 allocations: 0 bytes)
0x718308e795047519
And a demonstration of opting in to a faster fallback:
struct MyString <: AbstractString
s::String
end
m = MyString(s);

# 1.12
Base.iterate(m::MyString) = iterate(m.s)
Base.iterate(m::MyString, i::Integer) = iterate(m.s, i)
@btime hash($m)
204.583 μs (21 allocations: 107.02 KiB)
0x5fbd2717019846ea

# 1.13
Base.codeunit(m::MyString) = codeunit(m.s)
Base.codeunits(m::MyString) = codeunits(m.s)
@btime hash($m)
1.750 μs (0 allocations: 0 bytes)
0x718308e795047519
The hash for small fixed-width data has also changed. The final mixing step is now a single-round XMX construction with some carefully tuned constants, and the mixing step now properly avalanches when composing hash calls; previously the mixing step always simplified to a linear function at every composition depth. This change to the mixing step does introduce a data dependency (and thus potentially lower performance) when sequentially hashing elements together in a tight loop, e.g. foldr(hash, collection), but the algorithm for hashing AbstractArray has been partially unrolled at small to medium sizes, maintaining several hash accumulators in parallel, and will be much faster at most lengths.
Some important reminders: hash remains noncryptographic. Also, the default seed has changed. Custom hash methods should always accept the seed as an argument like hash(x::MyType, h::UInt) and never provide a default value like hash(x::MyType, h::UInt=0), since the correct seed is determined by the caller.
Faster GC by skipping image objects during marking
Cody Tapscott
Every Julia session starts with a large number of objects that were loaded from the system image, and every package that gets loaded brings its own package image with even more of them: method tables, type information, compiled code, constants and so on. These objects are never freed, and they are rarely mutated, yet until now a full garbage collection would walk through all of them to mark them as reachable, just like any other object on the heap. For a session with a handful of large packages loaded, this could easily be the dominant cost of a full collection.
In Julia 1.13, objects in the sysimage and in package images are loaded as permanently marked and the mark phase never enters them. The few mutations that do happen to image objects (for example, when a method is added to an existing function) are tracked separately so that any new objects they point to are still kept alive. The effect is that the cost of a full collection now scales with the size of the heap that your program actually created, not with the amount of code that has been loaded.
The easiest way to see the difference is to time a full collection in a fresh session:
# 1.12
julia> @time GC.gc()
0.035493 seconds (99.90% gc time)

# 1.13
julia> @time GC.gc()
0.000528 seconds (99.08% gc time)
The table below shows the time for a full collection (GC.gc(true)) on an Apple M4 Pro, first in a bare session and then after loading some packages of increasing size. Incremental (young generation) collections are not affected by this change and are equally fast on both versions.
1.121.13Bare session35 ms2 msusing Revise50 ms11 msusing Cthulhu59 ms18 msusing PythonCall90 ms30 msusing GLMakie187 ms68 ms
Since full collections are triggered more often for programs with a large live heap, this also shows up as reduced overall GC time in real workloads. The following example inserts random vectors into a Dict that is kept alive across iterations, so that a large fraction of the allocated objects get promoted to the old generation:
function work(n)
d = Dict{Int,Vector{Float64}}()
for i in 1:n
d[i % 50_000] = rand(64)
end
return length(d)
end

# 1.12
julia> @time work(5_000_000)
1.699095 seconds (10.00 M allocations: 2.688 GiB, 79.80% gc time)

# 1.13
julia> @time work(5_000_000)
0.566276 seconds (10.00 M allocations: 2.688 GiB, 44.32% gc time)
For more details, see the pull request.
Scheduler and interrupt fixes
Kiran Pamnany, Jameson Nash, Ian Butterworth
Idle threads now park in a dedicated scheduler task instead of holding on to the last task they ran, so finished tasks can be garbage collected promptly (#57544). This lands alongside a set of related scheduler fixes, including ones that make interrupts reliable again (#62665):

Ctrl-C reaches user code again, including scripts blocked in sleep or IO, and Distributed.interrupt works.

The REPL survives repeated and badly timed Ctrl-C presses.

@spawn wakes one idle thread in the task's threadpool instead of every thread (#61826). Spawn-heavy code speeds up anywhere from not at all on macOS, to 1.1-1.6x on a 16-core Linux machine, to 10-300x on Windows and heavily oversubscribed machines, where waking every thread had been the dominant cost.

Several lost-task and deadlock races were fixed.

Work on a proper task cancellation mechanism is in progress and is planned for Julia 1.14.
Introspection with type annotations
The code introspection macros (@which, @code_typed, @code_warntype, etc.) now accept call expressions where arguments are given as types instead of values, using the same ::T syntax as in method definitions and stacktraces. Values and types can be freely mixed, and keyword arguments are supported:
julia> @which push!(::Vector{Int}, 1)
push!(a::Vector{T}, item) where T
@ Base array.jl:1339

julia> @which sort!(::Vector{Int}; by = ::Function)
kwcall(::NamedTuple, ::typeof(sort!), v::AbstractVector{T}) where T
@ Base.Sort sort.jl:1734
This means a frame can be copied straight out of a stacktrace and pasted into @which to find the method that was called:
julia> @which Base.Order.lt(o::Base.Order.Lt{typeof(isless)}, a::Int64, b::Int64)
lt(o::Base.Order.Lt, a, b)
@ Base.Order ordering.jl:121
Broadcasting expressions are also supported in @code_lowered, @code_typed and @code_warntype:
julia> @code_warntype (::Vector{Int}) .+ 1.0
Tracing top-level evaluation with --trace-eval
Ian Butterworth
The new --trace-eval command-line flag shows top-level evaluation progress, to help see how a test suite or script is advancing, e.g. to identify hangs. For instance:
% julia --trace-eval script.jl
eval: #= /Users/me/.julia/config/startup.jl:1 =#
eval: #= /Users/me/.julia/config/startup.jl:2 =#
eval: #= /Users/me/.julia/config/startup.jl:3 =#
eval: #= script.jl:1 =#
eval: #= script.jl:2 =#
Hello world
It is also enabled automatically when the "debug logging" option is turned on for a CI run, as shown here for GitHub Actions:

JuliaC/trim
Cody Tapscott, many others
The juliac.jl script in the Julia repo has been made into a proper package/application: JuliaC.jl.
More code can now be trimmed, such as finalizers, @cfunction and mapreduce.
Several bugs in the trimming process itself were also fixed, improving its reliability.
Pkg
Kristoffer Carlsson
Pkg has received quite a bit of attention for 1.13. Here we list some of the more notable changes and improvements.
Change in default compression algorithm from gzip to zstd
For downloads from a package server (registries, packages and artifacts), Pkg will now by default ask for a zstd-compressed archive instead of a gzipped one. For the type of files Pkg typically downloads, zstd compression tends to have both a better compression ratio and significantly better decompression performance. As an example, downloading the packages and artifacts for the packages Plots, Makie and ModelingToolkit results in the following numbers:
gzipzstdTotal downloads405405Total download size307.99 MB239.31 MBTotal decompression time8.77 s5.50 sAverage decompression time21.98 ms13.77 ms
Performance improvements
Some micro-optimizations have been made to the resolver and the registry processing, leading to generally better performance of Pkg operations. Some of these improvements have already been backported to 1.12, so to get a proper performance comparison we compare against 1.12.1, which did not get any of these backports.
To assess the impact on resolver speed, we do the following benchmark: we add Plots to an empty environment, remove it, and then benchmark the time it takes to add Plots again. This ensures that all the files for Plots are already downloaded. In addition, auto-precompilation is turned off and the registry cache is cleared so that it has to be re-read from scratch. This means that the time spent adding Plots to this environment is mostly registry processing and resolving:
julia> ENV["JULIA_PKG_PRECOMPILE_AUTO"] = 0

# 1.12.1
julia> empty!(Pkg.Registry.REGISTRY_CACHE); @time Pkg.add("Plots"; io=devnull)
1.257017 seconds (8.83 M allocations: 681.328 MiB, 16.31% gc time)

# 1.13.0
julia> empty!(Pkg.Registry.REGISTRY_CACHE); @time Pkg.add("Plots"; io=devnull)
0.745170 seconds (4.43 M allocations: 304.580 MiB, 26.90% gc time)
In addition, Pkg will now clone repos with more efficient settings, avoiding downloading unnecessary data:
# 1.12.1
julia> @time Pkg.add(name="Plots"; rev="master")
Cloning git-repo `https://github.com/JuliaPlots/Plots.jl.git`
...
10.953074 seconds (4.51 M allocations: 330.819 MiB, 1.68% gc time)

# 1.13.0
julia> @time Pkg.add(name="Plots"; rev="master")
Cloning git-repo `https://github.com/JuliaPlots/Plots.jl.git`
...
2.980337 seconds (2.87 M allocations: 189.202 MiB, 3.87% gc time)
Registries for packages tracked in the manifest
Previously, to instantiate a manifest you needed to manually make sure that the registries required by that manifest were available. Now, the registry each package came from is recorded in the manifest and is automatically installed upon manifest instantiation (or other package operations).
Recursively collect sources
Pkg now recursively collects [sources] entries from packages fetched by URL, allowing private dependency chains to resolve without requiring all dependencies of a private package to be in a registry.
pkg> add now tries to add the same version as already-loaded packages
Julia has always allowed changing the active project during a session and supports stacked environments (most commonly via the default environment), which introduces a rough edge that can lead to repeated precompilation of packages. For instance, a version of a package is loaded from the default environment during startup.jl, and then the user adds a new package to the active project that pulls in a different version of that dependency. Pkg precompiles the dependency graph of the active project, so the new version gets precompiled even though the already-loaded version would often have satisfied the compat constraints just as well.
In 1.13, Pkg prefers the currently loaded version of any package that is already loaded when resolving pkg> add, if the environment's compatibility constraints allow it, so nothing needs to be precompiled again. As usual, pkg> status will flag that a newer version is available.
Pkg.test no longer defaults to enabling strict bounds checking
Previously, Pkg.test always launched the test process with --check-bounds=yes, which forces bounds checking even inside @inbounds blocks. Since precompile cache files are specific to the bounds-checking mode, this meant that the package being tested and all of its dependencies typically had to be recompiled before the tests could even start, and those cache files were then useless for normal development. Pkg.test now leaves the bounds-checking mode alone, so the test process inherits it from the parent Julia session and can reuse the precompile files generated during development. To get the old behavior, either start Julia with --check-bounds=yes before running Pkg.test, or pass the flag explicitly with Pkg.test(; julia_args=["--check-bounds=yes"]).
Juliaup GUI
Ian Butterworth
Juliaup, the Julia version manager, now has a graphical interface alongside its command line. It ships with Juliaup 1.22 and later on every platform Juliaup supports, so after a juliaup self update it can be opened with:
juliaup gui
The Installed tab shows each installed channel as a tile or a list row. From there a channel can be launched, launched with a custom project, arguments and environment variables, set as the default, or removed, and there are one-click actions to update everything and to garbage collect versions no channel uses any more.

The Available tab lists everything in the channel database, including release, lts, rc, nightly and pr{number} channels for testing pull requests, with an install button for each. It can also link an existing Julia binary to a custom channel name. The Configuration tab exposes Juliaup's settings, such as the version database update interval and automatic self-updates.

About
Get Help
Governance
Publications
Sponsors

Install
Manual Downloads
Source Code
Current Stable Release
Longterm Support Release

Documentation
YouTube
Getting Started
FAQ
Books

Community
Code of Conduct
Stewards
Diversity
JuliaCon
User/Developer Survey
Shop Merchandise

Contributing
Contributor's Guide
Issue Tracker
Report a Security Issue
Help Wanted Issues
Good First Issue
Dev Docs

This site is powered by Franklin.jl, and the Julia Programming Language.
©2026 JuliaLang.org contributors. The content on this website is made available under the MIT license.

Sponsor

Julia version 1.13 introduced numerous significant enhancements focusing on performance, developer experience, internal mechanisms, and package management. Performance improvements were highlighted through latency reductions, with precompiling packages taking roughly thirty percent less time compared to version 1.12 and between ten to twenty percent less time than version 1.10. This optimization extends to Time To First X (TTFX), which measures the time from starting Julia to obtaining the first result, as the community has systematically measured and optimized these costs. Further speedups were observed in startup time, which is approximately twenty percent faster than in version 1.12.

The Read-Eval-Print Loop (REPL) received substantial improvements focused on user interaction. This includes the introduction of syntax highlighting without relying on external packages, customizable color schemes, and a redesigned history search that functions similarly to the fzf command-line fuzzy finder, offering fuzzy searching, context about the REPL mode, and support for multi-selection. Additionally, bracketed paste functionality was extended to Windows for more efficient text input into the REPL. New macros, such as @__FUNCTION__, were introduced to address introspection, allowing references to the innermost containing function, improving API usability.

Changes to the hashing mechanism replaced the existing byte-hashing algorithm with RapidhashNano, which is used by default for types like AbstractString and large numeric values. This change is designed to offer superior performance by being a streaming hash that does not require input length upfront, and by moving the implementation to pure Julia for enhanced readability. While this change optimizes performance significantly for string hashing, it introduces a data dependency in the mixing step, which requires careful consideration when sequentially hashing elements in tight loops.

Garbage collection performance saw marked improvements by skipping image objects during the marking phase. This change fundamentally alters how Julia manages loaded system images and package images, preventing full garbage collection routines from traversing these large, static objects. This results in full collection times scaling with the actual heap allocated by the program rather than the volume of loaded code, leading to substantial reductions in collection time, especially in workloads with large live heaps. Scheduler and interrupt fixes aimed to enhance concurrency, ensuring that idle threads are managed by a dedicated scheduler task rather than blocking on the last executed task, and to restore reliability for interrupts, ensuring that signals like Ctrl-C reach user code effectively.

Introspection capabilities were enhanced through new type annotation macros. These macros now accept call expressions where arguments are specified as types instead of values, allowing for dynamic manipulation of stack traces and method discovery. This flexibility allows for broadcasting expressions within introspection operations, making it easier to navigate call hierarchies and method resolution. The command line also gained a feature for tracing top-level evaluation using the --trace-eval flag, which displays the progress of script execution, aiding in debugging hangs, and this tracing is automatically enabled in CI environments when debug logging is active.

Package management facilities, Pkg, received several performance and architectural updates. The default compression algorithm for downloading package artifacts and registries was switched from gzip to zstd, which provides better compression ratios and faster decompression speeds for typical file types. Micro-optimizations were also implemented in the resolver and registry processing to improve Pkg operation speeds, with some optimizations backported to earlier versions. In terms of repository cloning, Pkg now uses more efficient settings to avoid downloading unnecessary data. Furthermore, Pkg now recursively collects source entries from fetched URLs, enabling more efficient resolution of private dependency chains. Changes were also made to package testing, where Pkg.test no longer defaults to enabling strict bounds checking, allowing tests to reuse previously generated precompile files, managed by the parent Julia session.

Finally, the Juliaup environment manager was extended with a graphical user interface, providing an alternative way to manage installed channels, versions, and updates alongside the command-line tools.