Julia provides a variety of control flow constructs:
begin
and;
.if
-elseif
-else
and?:
(ternary operator).&&
(“and”) and||
(“or”), and also chained comparisons.while
andfor
.try
-catch
,error
andthrow
.yieldto
.The first five control flow mechanisms are standard to high-level programming languages.Task
s are not so standard: they provide non-local control flow, making it possible to switch between temporarily-suspended computations. This is a powerful construct: both exception handling and cooperative multitasking are implemented in Julia using tasks. Everyday programming requires no direct usage of tasks, but certain problems can be solved much more easily by using tasks.
Sometimes it is convenient to have a single expression which evaluates several subexpressions in order, returning the value of the last subexpression as its value. There are two Julia constructs that accomplish this:begin
blocks and;
chains. The value of both compound expression constructs is that of the last subexpression. Here's an example of abegin
block:
julia> z = begin x = 1 y = 2 x + y end3
Since these are fairly small, simple expressions, they could easily be placed onto a single line, which is where the;
chain syntax comes in handy:
julia> z = (x = 1; y = 2; x + y)3
This syntax is particularly useful with the terse single-line function definition form introduced inFunctions. Although it is typical, there is no requirement thatbegin
blocks be multiline or that;
chains be single-line:
julia> begin x = 1; y = 2; x + y end3julia> (x = 1; y = 2; x + y)3
Conditional evaluation allows portions of code to be evaluated or not evaluated depending on the value of a boolean expression. Here is the anatomy of theif
-elseif
-else
conditional syntax:
if x < y println("x is less than y")elseif x > y println("x is greater than y")else println("x is equal to y")end
If the condition expressionx < y
istrue
, then the corresponding block is evaluated; otherwise the condition expressionx > y
is evaluated, and if it istrue
, the corresponding block is evaluated; if neither expression is true, theelse
block is evaluated. Here it is in action:
julia> function test(x, y) if x < y println("x is less than y") elseif x > y println("x is greater than y") else println("x is equal to y") end endtest (generic function with 1 method)julia> test(1, 2)x is less than yjulia> test(2, 1)x is greater than yjulia> test(1, 1)x is equal to y
Theelseif
andelse
blocks are optional, and as manyelseif
blocks as desired can be used. The condition expressions in theif
-elseif
-else
construct are evaluated until the first one evaluates totrue
, after which the associated block is evaluated, and no further condition expressions or blocks are evaluated.
if
blocks are "leaky", i.e. they do not introduce a local scope. This means that new variables defined inside theif
clauses can be used after theif
block, even if they weren't defined before. So, we could have defined thetest
function above as
julia> function test(x,y) if x < y relation = "less than" elseif x == y relation = "equal to" else relation = "greater than" end println("x is ", relation, " y.") endtest (generic function with 1 method)julia> test(2, 1)x is greater than y.
The variablerelation
is declared inside theif
block, but used outside. However, when depending on this behavior, make sure all possible code paths define a value for the variable. The following change to the above function results in a runtime error
julia> function test(x,y) if x < y relation = "less than" elseif x == y relation = "equal to" end println("x is ", relation, " y.") endtest (generic function with 1 method)julia> test(1,2)x is less than y.julia> test(2,1)ERROR: UndefVarError: `relation` not defined in local scopeStacktrace: [1] test(::Int64, ::Int64) at ./none:7
if
blocks also return a value, which may seem unintuitive to users coming from many other languages. This value is simply the return value of the last executed statement in the branch that was chosen, so
julia> x = 33julia> if x > 0 "positive!" else "negative..." end"positive!"
Note that very short conditional statements (one-liners) are frequently expressed using Short-Circuit Evaluation in Julia, as outlined in the next section.
Unlike C, MATLAB, Perl, Python, and Ruby – but like Java, and a few other stricter, typed languages – it is an error if the value of a conditional expression is anything buttrue
orfalse
:
julia> if 1 println("true") endERROR: TypeError: non-boolean (Int64) used in boolean context
This error indicates that the conditional was of the wrong type:Int64
rather than the requiredBool
.
The so-called "ternary operator",?:
, is closely related to theif
-elseif
-else
syntax, but is used where a conditional choice between single expression values is required, as opposed to conditional execution of longer blocks of code. It gets its name from being the only operator in most languages taking three operands:
a ? b : c
The expressiona
, before the?
, is a condition expression, and the ternary operation evaluates the expressionb
, before the:
, if the conditiona
istrue
or the expressionc
, after the:
, if it isfalse
. Note that the spaces around?
and:
are mandatory: an expression likea?b:c
is not a valid ternary expression (but a newline is acceptable after both the?
and the:
).
The easiest way to understand this behavior is to see an example. In the previous example, theprintln
call is shared by all three branches: the only real choice is which literal string to print. This could be written more concisely using the ternary operator. For the sake of clarity, let's try a two-way version first:
julia> x = 1; y = 2;julia> println(x < y ? "less than" : "not less than")less thanjulia> x = 1; y = 0;julia> println(x < y ? "less than" : "not less than")not less than
If the expressionx < y
is true, the entire ternary operator expression evaluates to the string"less than"
and otherwise it evaluates to the string"not less than"
. The original three-way example requires chaining multiple uses of the ternary operator together:
julia> test(x, y) = println(x < y ? "x is less than y" : x > y ? "x is greater than y" : "x is equal to y")test (generic function with 1 method)julia> test(1, 2)x is less than yjulia> test(2, 1)x is greater than yjulia> test(1, 1)x is equal to y
To facilitate chaining, the operator associates from right to left.
It is significant that likeif
-elseif
-else
, the expressions before and after the:
are only evaluated if the condition expression evaluates totrue
orfalse
, respectively:
julia> v(x) = (println(x); x)v (generic function with 1 method)julia> 1 < 2 ? v("yes") : v("no")yes"yes"julia> 1 > 2 ? v("yes") : v("no")no"no"
The&&
and||
operators in Julia correspond to logical “and” and “or” operations, respectively, and are typically used for this purpose. However, they have an additional property ofshort-circuit evaluation: they don't necessarily evaluate their second argument, as explained below. (There are also bitwise&
and|
operators that can be used as logical “and” and “or”without short-circuit behavior, but beware that&
and|
have higher precedence than&&
and||
for evaluation order.)
Short-circuit evaluation is quite similar to conditional evaluation. The behavior is found in most imperative programming languages having the&&
and||
boolean operators: in a series of boolean expressions connected by these operators, only the minimum number of expressions are evaluated as are necessary to determine the final boolean value of the entire chain. Some languages (like Python) refer to them asand
(&&
) andor
(||
). Explicitly, this means that:
a && b
, the subexpressionb
is only evaluated ifa
evaluates totrue
.a || b
, the subexpressionb
is only evaluated ifa
evaluates tofalse
.The reasoning is thata && b
must befalse
ifa
isfalse
, regardless of the value ofb
, and likewise, the value ofa || b
must be true ifa
istrue
, regardless of the value ofb
. Both&&
and||
associate to the right, but&&
has higher precedence than||
does. It's easy to experiment with this behavior:
julia> t(x) = (println(x); true)t (generic function with 1 method)julia> f(x) = (println(x); false)f (generic function with 1 method)julia> t(1) && t(2)12truejulia> t(1) && f(2)12falsejulia> f(1) && t(2)1falsejulia> f(1) && f(2)1falsejulia> t(1) || t(2)1truejulia> t(1) || f(2)1truejulia> f(1) || t(2)12truejulia> f(1) || f(2)12false
You can easily experiment in the same way with the associativity and precedence of various combinations of&&
and||
operators.
This behavior is frequently used in Julia to form an alternative to very shortif
statements. Instead ofif <cond> <statement> end
, one can write<cond> && <statement>
(which could be read as: <cond>and then <statement>). Similarly, instead ofif ! <cond> <statement> end
, one can write<cond> || <statement>
(which could be read as: <cond>or else <statement>).
For example, a recursive factorial routine could be defined like this:
julia> function fact(n::Int) n >= 0 || error("n must be non-negative") n == 0 && return 1 n * fact(n-1) endfact (generic function with 1 method)julia> fact(5)120julia> fact(0)1julia> fact(-1)ERROR: n must be non-negativeStacktrace: [1] error at ./error.jl:33 [inlined] [2] fact(::Int64) at ./none:2 [3] top-level scope
Boolean operationswithout short-circuit evaluation can be done with the bitwise boolean operators introduced inMathematical Operations and Elementary Functions:&
and|
. These are normal functions, which happen to support infix operator syntax, but always evaluate their arguments:
julia> f(1) & t(2)12falsejulia> t(1) | t(2)12true
Just like condition expressions used inif
,elseif
or the ternary operator, the operands of&&
or||
must be boolean values (true
orfalse
). Using a non-boolean value anywhere except for the last entry in a conditional chain is an error:
julia> 1 && trueERROR: TypeError: non-boolean (Int64) used in boolean context
On the other hand, any type of expression can be used at the end of a conditional chain. It will be evaluated and returned depending on the preceding conditionals:
julia> true && (x = (1, 2, 3))(1, 2, 3)julia> false && (x = (1, 2, 3))false
There are two constructs for repeated evaluation of expressions: thewhile
loop and thefor
loop. Here is an example of awhile
loop:
julia> i = 1;julia> while i <= 3 println(i) global i += 1 end123
Thewhile
loop evaluates the condition expression (i <= 3
in this case), and as long it remainstrue
, keeps also evaluating the body of thewhile
loop. If the condition expression isfalse
when thewhile
loop is first reached, the body is never evaluated.
Thefor
loop makes common repeated evaluation idioms easier to write. Since counting up and down like the abovewhile
loop does is so common, it can be expressed more concisely with afor
loop:
julia> for i = 1:3 println(i) end123
Here the1:3
is arange
object, representing the sequence of numbers 1, 2, 3. Thefor
loop iterates through these values, assigning each one in turn to the variablei
. In general, thefor
construct can loop over any "iterable" object (or "container"), from a range like1:3
or1:3:13
(aStepRange
indicating every 3rd integer 1, 4, 7, …, 13) to more generic containers like arrays, includingiterators defined by user code or external packages. For containers other than ranges, the alternative (but fully equivalent) keywordin
or∈
is typically used instead of=
, since it makes the code read more clearly:
julia> for i in [1,4,0] println(i) end140julia> for s ∈ ["foo","bar","baz"] println(s) endfoobarbaz
Various types of iterable containers will be introduced and discussed in later sections of the manual (see, e.g.,Multi-dimensional Arrays).
One rather important distinction between the previouswhile
loop form and thefor
loop form is the scope during which the variable is visible. Afor
loop always introduces a new iteration variable in its body, regardless of whether a variable of the same name exists in the enclosing scope. This implies that on the one handi
need not be declared before the loop. On the other hand it will not be visible outside the loop, nor will an outside variable of the same name be affected. You'll either need a new interactive session instance or a different variable name to test this:
julia> for j = 1:3 println(j) end123julia> jERROR: UndefVarError: `j` not defined in `Main`
julia> j = 0;julia> for j = 1:3 println(j) end123julia> j0
Usefor outer
to modify the latter behavior and reuse an existing local variable.
SeeScope of Variables for a detailed explanation of variable scope,outer
, and how it works in Julia.
It is sometimes convenient to terminate the repetition of awhile
before the test condition is falsified or stop iterating in afor
loop before the end of the iterable object is reached. This can be accomplished with thebreak
keyword:
julia> i = 1;julia> while true println(i) if i >= 3 break end global i += 1 end123julia> for j = 1:1000 println(j) if j >= 3 break end end123
Without thebreak
keyword, the abovewhile
loop would never terminate on its own, and thefor
loop would iterate up to 1000. These loops are both exited early by usingbreak
.
In other circumstances, it is handy to be able to stop an iteration and move on to the next one immediately. Thecontinue
keyword accomplishes this:
julia> for i = 1:10 if i % 3 != 0 continue end println(i) end369
This is a somewhat contrived example since we could produce the same behavior more clearly by negating the condition and placing theprintln
call inside theif
block. In realistic usage there is more code to be evaluated after thecontinue
, and often there are multiple points from which one callscontinue
.
Multiple nestedfor
loops can be combined into a single outer loop, forming the cartesian product of its iterables:
julia> for i = 1:2, j = 3:4 println((i, j)) end(1, 3)(1, 4)(2, 3)(2, 4)
With this syntax, iterables may still refer to outer loop variables; e.g.for i = 1:n, j = 1:i
is valid. However abreak
statement inside such a loop exits the entire nest of loops, not just the inner one. Both variables (i
andj
) are set to their current iteration values each time the inner loop runs. Therefore, assignments toi
will not be visible to subsequent iterations:
julia> for i = 1:2, j = 3:4 println((i, j)) i = 0 end(1, 3)(1, 4)(2, 3)(2, 4)
If this example were rewritten to use afor
keyword for each variable, then the output would be different: the second and fourth values would contain0
.
Multiple containers can be iterated over at the same time in a singlefor
loop usingzip
:
julia> for (j, k) in zip([1 2 3], [4 5 6 7]) println((j,k)) end(1, 4)(2, 5)(3, 6)
Usingzip
will create an iterator that is a tuple containing the subiterators for the containers passed to it. Thezip
iterator will iterate over all subiterators in order, choosing the$i$th element of each subiterator in the$i$th iteration of thefor
loop. Once any of the subiterators run out, thefor
loop will stop.
When an unexpected condition occurs, a function may be unable to return a reasonable value to its caller. In such cases, it may be best for the exceptional condition to either terminate the program while printing a diagnostic error message, or if the programmer has provided code to handle such exceptional circumstances then allow that code to take the appropriate action.
Exception
sException
s are thrown when an unexpected condition has occurred. The built-inException
s listed below all interrupt the normal flow of control.
For example, thesqrt
function throws aDomainError
if applied to a negative real value:
julia> sqrt(-1)ERROR: DomainError with -1.0:sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)).Stacktrace:[...]
You may define your own exceptions in the following way:
julia> struct MyCustomException <: Exception end
throw
functionExceptions can be created explicitly withthrow
. For example, a function defined only for non-negative numbers could be written tothrow
aDomainError
if the argument is negative:
julia> f(x) = x>=0 ? exp(-x) : throw(DomainError(x, "argument must be non-negative"))f (generic function with 1 method)julia> f(1)0.36787944117144233julia> f(-1)ERROR: DomainError with -1:argument must be non-negativeStacktrace: [1] f(::Int64) at ./none:1
Note thatDomainError
without parentheses is not an exception, but a type of exception. It needs to be called to obtain anException
object:
julia> typeof(DomainError(nothing)) <: Exceptiontruejulia> typeof(DomainError) <: Exceptionfalse
Additionally, some exception types take one or more arguments that are used for error reporting:
julia> throw(UndefVarError(:x))ERROR: UndefVarError: `x` not defined
This mechanism can be implemented easily by custom exception types following the wayUndefVarError
is written:
julia> struct MyUndefVarError <: Exception var::Symbol endjulia> Base.showerror(io::IO, e::MyUndefVarError) = print(io, e.var, " not defined")
When writing an error message, it is preferred to make the first word lowercase. For example,
size(A) == size(B) || throw(DimensionMismatch("size of A not equal to size of B"))
is preferred over
size(A) == size(B) || throw(DimensionMismatch("Size of A not equal to size of B"))
.
However, sometimes it makes sense to keep the uppercase first letter, for instance if an argument to a function is a capital letter:
size(A,1) == size(B,2) || throw(DimensionMismatch("A has first dimension..."))
.
Theerror
function is used to produce anErrorException
that interrupts the normal flow of control.
Suppose we want to stop execution immediately if the square root of a negative number is taken. To do this, we can define a fussy version of thesqrt
function that raises an error if its argument is negative:
julia> fussy_sqrt(x) = x >= 0 ? sqrt(x) : error("negative x not allowed")fussy_sqrt (generic function with 1 method)julia> fussy_sqrt(2)1.4142135623730951julia> fussy_sqrt(-1)ERROR: negative x not allowedStacktrace: [1] error at ./error.jl:33 [inlined] [2] fussy_sqrt(::Int64) at ./none:1 [3] top-level scope
Iffussy_sqrt
is called with a negative value from another function, instead of trying to continue execution of the calling function, it returns immediately, displaying the error message in the interactive session:
julia> function verbose_fussy_sqrt(x) println("before fussy_sqrt") r = fussy_sqrt(x) println("after fussy_sqrt") return r endverbose_fussy_sqrt (generic function with 1 method)julia> verbose_fussy_sqrt(2)before fussy_sqrtafter fussy_sqrt1.4142135623730951julia> verbose_fussy_sqrt(-1)before fussy_sqrtERROR: negative x not allowedStacktrace: [1] error at ./error.jl:33 [inlined] [2] fussy_sqrt at ./none:1 [inlined] [3] verbose_fussy_sqrt(::Int64) at ./none:3 [4] top-level scope
try/catch
statementThetry/catch
statement allows forException
s to be tested for, and for the graceful handling of things that may ordinarily break your application. For example, in the below code the function for square root would normally throw an exception. By placing atry/catch
block around it we can mitigate that here. You may choose how you wish to handle this exception, whether logging it, return a placeholder value or as in the case below where we just printed out a statement. One thing to think about when deciding how to handle unexpected situations is that using atry/catch
block is much slower than using conditional branching to handle those situations. Below there are more examples of handling exceptions with atry/catch
block:
julia> try sqrt("ten") catch e println("You should have entered a numeric value") endYou should have entered a numeric value
try/catch
statements also allow theException
to be saved in a variable. The following contrived example calculates the square root of the second element ofx
ifx
is indexable, otherwise assumesx
is a real number and returns its square root:
julia> sqrt_second(x) = try sqrt(x[2]) catch y if isa(y, DomainError) sqrt(complex(x[2], 0)) elseif isa(y, BoundsError) sqrt(x) end endsqrt_second (generic function with 1 method)julia> sqrt_second([1 4])2.0julia> sqrt_second([1 -4])0.0 + 2.0imjulia> sqrt_second(9)3.0julia> sqrt_second(-9)ERROR: DomainError with -9.0:sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)).Stacktrace:[...]
Note that the symbol followingcatch
will always be interpreted as a name for the exception, so care is needed when writingtry/catch
expressions on a single line. The following code willnot work to return the value ofx
in case of an error:
try bad() catch x end
Instead, use a semicolon or insert a line break aftercatch
:
try bad() catch; x endtry bad()catch xend
The power of thetry/catch
construct lies in the ability to unwind a deeply nested computation immediately to a much higher level in the stack of calling functions. There are situations where no error has occurred, but the ability to unwind the stack and pass a value to a higher level is desirable. Julia provides therethrow
,backtrace
,catch_backtrace
andcurrent_exceptions
functions for more advanced error handling.
else
ClausesThis functionality requires at least Julia 1.8.
In some cases, one may not only want to appropriately handle the error case, but also want to run some code only if thetry
block succeeds. For this, anelse
clause can be specified after thecatch
block that is run whenever no error was thrown previously. The advantage over including this code in thetry
block instead is that any further errors don't get silently caught by thecatch
clause.
local xtry x = read("file", String)catch # handle read errorselse # do something with xend
Thetry
,catch
,else
, andfinally
clauses each introduce their own scope blocks, so if a variable is only defined in thetry
block, it can not be accessed by theelse
orfinally
clause:
julia> try foo = 1 catch else foo endERROR: UndefVarError: `foo` not defined in `Main`Suggestion: check for spelling errors or missing imports.
Use thelocal
keyword outside thetry
block to make the variable accessible from anywhere within the outer scope.
finally
ClausesIn code that performs state changes or uses resources like files, there is typically clean-up work (such as closing files) that needs to be done when the code is finished. Exceptions potentially complicate this task, since they can cause a block of code to exit before reaching its normal end. Thefinally
keyword provides a way to run some code when a given block of code exits, regardless of how it exits.
For example, here is how we can guarantee that an opened file is closed:
f = open("file")try # operate on file ffinally close(f)end
When control leaves thetry
block (for example due to areturn
, or just finishing normally),close(f)
will be executed. If thetry
block exits due to an exception, the exception will continue propagating. Acatch
block may be combined withtry
andfinally
as well. In this case thefinally
block will run aftercatch
has handled the error.
Tasks are a control flow feature that allows computations to be suspended and resumed in a flexible manner. We mention them here only for completeness; for a full discussion seeAsynchronous Programming.
Settings
This document was generated withDocumenter.jl version 1.8.0 onWednesday 9 July 2025. Using Julia version 1.11.6.