# Cartesian For Loop

**URL:** <https://discourse.julialang.org/t/cartesian-for-loop/130353>\
**Category:** General Usage\
**Tags:** question\
**Created:** [June 30, 2025, 11:47pm UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353 "2025-06-30T23:47:39Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [June 30, 2025, 11:47pm UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/1 "2025-06-30T23:47:39Z")

</div>

Here are three (I, II, III) implementations.

```julia
import Random.seed! as seed!
import Random.shuffle as shuffle

seed!(1); # [Code I]
for i = shuffle(1:3)
    for j = shuffle(1:4)
        println("($i, $j)")
    end
end

seed!(1); # [Code II]
for i = shuffle(1:3), j = shuffle(1:4)
    println("($i, $j)") # its inner layer sequence varies!
end

seed!(1); # [Code III]
let I = shuffle(1:3), J = shuffle(1:4)
    @show I J;
    for i = I, j = J
        println("($i, $j)") # its inner layer sequence are the same.
    end
end;

```

In my intuition (and I believe it should be so), `Code II` **should resolves into** `Code III`. (cf. my intuition [here](https://discourse.julialang.org/t/let-x-1-parseerror/130050/7)).  
But the current behavior implies as if `Code II` instead resolves into `Code I`, which I think is not understandable.

The style in `Code I` and `Code II` are very different—e.g., only one `break` is entailed to escape the `for` in `Code II`, whereas you need two in `Code I`.

---

<div class="post-metadata">

**Author:** ![Jeff\_Emanuel](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jeff_emanuel/32/15440_2.png) [@Jeff\_Emanuel](https://discourse.julialang.org/u/Jeff_Emanuel)\
**Post date:** [June 30, 2025, 11:59pm UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/2 "2025-06-30T23:59:28Z")

</div>

In the first two examples, you call `shuffle(1:4)` creating a new iterator in each iteration of the `i` loop. In the last example you call `shuffle(1:4)` once and reuse same iterator. It’s as if

```julia
julia> print(collect(shuffle(1:4)))
[1, 2, 3, 4]
julia> print(collect(shuffle(1:4)))
[3, 1, 2, 4]
julia> print(collect(shuffle(1:4)))
[4, 3, 1, 2]

```

vs

```julia
julia> j=shuffle(1:4)
4-element Vector{Int64}:
 2
 1
 3
 4

julia> print(collect(j))
[2, 1, 3, 4]
julia> print(collect(j))
[2, 1, 3, 4]
julia> print(collect(j))
[2, 1, 3, 4]

```

`collect` calls included to clarify that you are iterating the same (or different) iterators, although the iterators are simply vectors here.

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [July 1, 2025, 12:08am UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/3 "2025-07-01T00:08:33Z")

</div>

Sorry but I don’t think you’ve clarified my confusion. The `collect` is not relevant here.

```julia
julia> shuffle(1:3)
3-element Vector{Int64}:
 3
 1
 2

```

It’s already a vector.

> [@Jeff\_Emanuel](#):
>
> In the first two examples, you call `shuffle(1:4)` creating a new iterator in each iteration of the `i` loop.

I don’t think so.

In my way to understand it,

```julia
julia> for i = shuffle(1:9)
       println(i)
       end

8
1
3
9
2
6
4
7
5

```

is equivalent to

```julia
let I = shuffle(1:9)
    for i = I
        println(i)
    end
end

```

isn’t it?

If this logic is true, then it should extends to the Cartesian for loop in my first post. (But the behavior is distinct, I’m wondering this)

---

<div class="post-metadata">

**Author:** ![Jeff\_Emanuel](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jeff_emanuel/32/15440_2.png) [@Jeff\_Emanuel](https://discourse.julialang.org/u/Jeff_Emanuel)\
**Post date:** [July 1, 2025, 12:18am UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/4 "2025-07-01T00:18:49Z")

</div>

It’s the j loop that you are reinitializing for each i

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [July 1, 2025, 12:20am UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/5 "2025-07-01T00:20:24Z")

</div>

> [@WalterMadelim](#):
>
> I don’t think so.

It really is, maybe we just need a louder example:

```julia
julia> struct PrintRange{R<:AbstractRange} r::R end

julia> PrintRange(r::R) where R<:AbstractRange = (println("hello, new range $(r)!"); PrintRange{R}(r))
PrintRange

julia> Base.iterate(pr::PrintRange, args...) = iterate(pr.r, args...)

julia> for i in PrintRange(1:2), j in PrintRange(3:4)
         println(i, j)
       end
hello, new range 1:2!
hello, new range 3:4!
13
14
hello, new range 3:4!
23
24

```

This is documented under [Control Flow](https://docs.julialang.org/en/v1/manual/control-flow/#man-loops):

> Multiple nested `for` loops can be combined into a single outer loop, forming the \>cartesian product of its iterables:
> 
> ```julia
> 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 a `break` statement inside such a loop exits the entire nest of loops, not just the inner one.

`PrintRange(3:4)` executed for each `i` value (Code I behavior) because it could have depended on it, not together with `PrintRange(1:2)` in the header prior to all iterations as you expected (Code III behavior). Code I+II and Code III are semantically different, so 1) the results can differ if the inner loop’s call does not only depend on its arguments, as you demonstrated with a PRNG, and 2) if the call is expensive, Code III saves time at no cost. Common subexpression elimination may optimize Code I+II to an equivalent of Code III if the compiler recognizes the inner call is pure, but that’s not as reliable as us writing what we mean, and sometimes we really would like side effects like `sleep`.

There’s nothing that can be done about the `break` exiting all the loops of the same block; if it only exited the inner one, then there’s no way to exit any of the other loops. `break`-ing out of loops separately is already an option with multiple nested blocks. `continue` does work across the inner loop either way because it skips to the next iteration.

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [July 1, 2025, 12:43am UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/6 "2025-07-01T00:43:17Z")

</div>

So, `Code II` does not resolve into `Code III`, nor does it resolve into `Code I`.  
I got it. Thank you.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [July 1, 2025, 12:49am UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/7 "2025-07-01T00:49:10Z")

</div>

I’m not sure what you mean by resolve, but all 3 versions certainly parse differently, and the multiple blocks in Code II is only practically equivalent to the single block in Code I because only the innermost `for`-block has code for iterations which lacks a `break`. If you need to write code in between `for` blocks or need single-level `break`s, then Code II is not usable. The merged for-loop syntax of Code II is really only useful for that all-level `break` when you can’t easily combine all of the possibly interdependent loop iterables into one big one.

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [July 1, 2025, 2:09am UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/8 "2025-07-01T02:09:53Z")

</div>

> [@Benny](#):
>
> I’m not sure what you mean by resolve

Literal meaning: `resolve into something := to gradually become or be understood as something`.

The info you provided is very useful!  
I think the present behavior is good.

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [July 29, 2025, 4:38am UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/9 "2025-07-29T04:38:41Z")

</div>

> [@WalterMadelim](#):
>
> In my way to understand it,
> 
> ```julia-auto
> julia> for i = shuffle(1:9)
> println(i)
> end
> 
> 8
> 1
> 3
> 9
> 2
> 6
> 4
> 7
> 5
> 
> ```
> 
> is equivalent to
> 
> ```julia-auto
> let I = shuffle(1:9)
> for i = I
> println(i)
> end
> end
> 
> ```
> 
> isn’t it?

That was completely an illusion.  
The iterator is updated dynamically in _each_ iteration.  
This illustrates:

```julia
julia> d = Dict(i => 10i for i = 1:5)
Dict{Int64, Int64} with 5 entries:
  5 => 50
  4 => 40
  2 => 20
  3 => 30
  1 => 10

julia> for (k, v) = d
           println("k = $k, v = $v")
           if k == 4
               pop!(d, 3)
               d[2] *= -1
           end
           println(d)
       end
k = 5, v = 50
Dict(5 => 50, 4 => 40, 2 => 20, 3 => 30, 1 => 10)
k = 4, v = 40
Dict(5 => 50, 4 => 40, 2 => -20, 1 => 10)
k = 2, v = -20
Dict(5 => 50, 4 => 40, 2 => -20, 1 => 10)
k = 1, v = 10
Dict(5 => 50, 4 => 40, 2 => -20, 1 => 10)

julia> d = Dict(i => 10i for i = 1:5)
Dict{Int64, Int64} with 5 entries:
  5 => 50
  4 => 40
  2 => 20
  3 => 30
  1 => 10

julia> let d_const = d, d = copy(d)
           for (k, v) = d_const
               println("k = $k, v = $v")
               if k == 4
                   pop!(d, 3)
                   d[2] *= -1
               end
               println("d = $d")
           end
       end
k = 5, v = 50
d = Dict(5 => 50, 4 => 40, 2 => 20, 3 => 30, 1 => 10)
k = 4, v = 40
d = Dict(5 => 50, 4 => 40, 2 => -20, 1 => 10)
k = 2, v = 20
d = Dict(5 => 50, 4 => 40, 2 => -20, 1 => 10)
k = 3, v = 30
d = Dict(5 => 50, 4 => 40, 2 => -20, 1 => 10)
k = 1, v = 10
d = Dict(5 => 50, 4 => 40, 2 => -20, 1 => 10)

```

---

<div class="post-metadata">

**Author:** ![sgaure](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sgaure/32/14779_2.png) [@sgaure](https://discourse.julialang.org/u/sgaure)\
**Post date:** [July 29, 2025, 6:28am UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/10 "2025-07-29T06:28:50Z")

</div>

> [@WalterMadelim](#):
>
> ```julia-auto
> julia> for i = shuffle(1:9)
> println(i)
> end
> 
> ```
> 
> …  
> The iterator is updated dynamically in _each_ iteration.

That’s not exactly true. From [Interfaces · The Julia Language](https://docs.julialang.org/en/v1/manual/interfaces/#man-interface-iteration), on the `Base.iterate` function:

> [@](#):
>
> Sequential iteration is implemented by the [`iterate`](https://docs.julialang.org/en/v1/base/collections/#Base.iterate) function. Instead of mutating objects as they are iterated over, Julia iterators may keep track of the iteration state externally from the object. The return value from iterate is always either a tuple of a value and a state, or `nothing` if no elements remain. The state object will be passed back to the iterate function on the next iteration and is generally considered an implementation detail private to the iterable object.
> 
> Any object that defines this function is iterable and can be used in the [many functions that rely upon iteration](https://docs.julialang.org/en/v1/base/collections/#lib-collections-iteration). It can also be used directly in a [`for`](https://docs.julialang.org/en/v1/base/base/#for) loop since the syntax:
> 
> ```julia-auto
> for item in iter # or "for item = iter"
> # body
> end
> 
> ```
> 
> is translated into:
> 
> ```julia-auto
> next = iterate(iter)
> while next !== nothing
> (item, state) = next
> # body
> next = iterate(iter, state)
> end
> 
> ```

So in fact, an iterator in julia is not normally updated. What exactly happens with your `Dict` example, I don’t know. I think updating a `Dict` while you iterate over it is undefined.

---

<div class="post-metadata">

**Author:** ![eldee](https://avatars.discourse-cdn.com/v4/letter/e/b5a626/32.png) [@eldee](https://discourse.julialang.org/u/eldee)\
**Post date:** [July 29, 2025, 8:22am UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/11 "2025-07-29T08:22:00Z")

</div>

> [@WalterMadelim](#):
>
> That was completely an illusion.  
> The iterator is updated dynamically in _each_ iteration.

I don’t see the connection between these two statements, but the code blocks you quoted are really equivalent, with you calling `shuffle(1:9)` only once for both versions.

> [@sgaure](#):
>
> What exactly happens with your `Dict` example, I don’t know.

The iteration state is just the index of the next entry in the underlying `Memory` to potentially return (if the slot is used). If you modify the `Dict` (inplace) during the iteration, this will now point to some other content.

```julia-repl
julia> d = Dict("a" => 1, "b" => 2)
Dict{String, Int64} with 2 entries:
  "b" => 2
  "a" => 1

julia> d.keys
16-element Memory{String}:
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
 #undef
    "b"
 #undef
 #undef
 #undef
 #undef
    "a"
 #undef
 #undef

julia> iterate(d) # state is index corresponding to "b", + 1
("b" => 2, 10)

julia> for (x, y) in d
           println("$x => $y")
           if y == 2 # in first iteration
               d["a"] = -1
               # The next valid slot iterate will encounter corresponds to "a".
               # The value here is now -1 instead of 1.
           end
       end
b => 2
a => -1

```

---

<div class="post-metadata">

**Author:** ![sgaure](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sgaure/32/14779_2.png) [@sgaure](https://discourse.julialang.org/u/sgaure)\
**Post date:** [July 29, 2025, 8:43am UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/12 "2025-07-29T08:43:38Z")

</div>

> [@eldee](#):
>
> The iteration state is just the index of the next entry in the underlying `Memory` to potentially return (if the slot is used). If you modify the `Dict` (inplace) during the iteration, this will now point to some other content.

True, but unexpected things may happen:

```julia-auto
julia> d = Dict('a':'j' .=> 1:10)
Dict{Char, Int64} with 10 entries:
  'f' => 6
  'g' => 7
  'a' => 1
  'c' => 3
  'd' => 4
  'e' => 5
  'h' => 8
  'i' => 9
  'j' => 10
  'b' => 2

julia> for (x,y) in d
           println("$x => $y")
           if y == 2
               d['k'] = 11
           end
       end
f => 6
g => 7
a => 1
c => 3
d => 4
e => 5
h => 8
i => 9
j => 10
b => 2
h => 8
j => 10
i => 9
k => 11
a => 1
c => 3
g => 7
b => 2

```

---

<div class="post-metadata">

**Author:** ![eldee](https://avatars.discourse-cdn.com/v4/letter/e/b5a626/32.png) [@eldee](https://discourse.julialang.org/u/eldee)\
**Post date:** [July 29, 2025, 8:51am UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/13 "2025-07-29T08:51:01Z")

</div>

Indeed. Adding the extra entry here causes the underlying `Memory` to expand and reorder, so that the state index makes little sense any more. So obviously it’s indeed best to not modify the object you’re currently iterating over! 🙂

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [July 29, 2025, 9:12am UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/14 "2025-07-29T09:12:37Z")

</div>

> [@eldee](#):
>
> I don’t see the connection between these two statements

For me, `for` and `let` share some common features, e.g.

```julia
julia> for _ = 1:1
           i = 1
           let i = 2
           end
           println(i)
       end
1

julia> for _ = 1:1
           i = 1
           let
               i = 2
           end
           println(i)
       end
2

julia> for _ = 1:1
           i = 1
           for i = 1:2
           end
           println(i)
       end
1

julia> for _ = 1:1
           i = 1
           for k = 1:2
               i = k
           end
           println(i)
       end
2

julia> for i = 1:5, j = i:5, k = i+j:5 # valid syntax
       end

julia> let i = 1, j = i+2, k = i+j # valid syntax
       end

```

But I wonder **why julia don’t allow users to write the following code**

```julia
local i = 1, j = i+2, k = i+j # define 3 local names initially

```

---

<div class="post-metadata">

**Author:** ![\_bernhard](https://avatars.discourse-cdn.com/v4/letter/_/bc79bd/32.png) [@\_bernhard](https://discourse.julialang.org/u/_bernhard)\
**Post date:** [July 29, 2025, 9:23am UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/15 "2025-07-29T09:23:51Z")

</div>

This way of writing it is only valid as part of a `let` block.  
Just change it to

```julia-auto
local i = 1
local j = i + 2
local k = i + j

```

---

<div class="post-metadata">

**Author:** ![sgaure](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sgaure/32/14779_2.png) [@sgaure](https://discourse.julialang.org/u/sgaure)\
**Post date:** [July 29, 2025, 9:56am UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/16 "2025-07-29T09:56:00Z")

</div>

> [@WalterMadelim](#):
>
> But I wonder **why julia don’t allow users to write the following code**
> 
> ```julia-auto
> local i = 1, j = i+2, k = i+j # define 3 local names initially
> 
> ```

I suspect it’s because it would introduce complications in the parser. It’s possible to declare several local variables without assigning a value to them:

```julia-auto
local i, j, k

```

It’s possible to initialize a single one to a tuple:

```julia-auto
local i = 1, 2
# i == (1, 2)

```

Even things like

```julia-auto
j = 3
local i = 1, j == 2
# i == (1, false)

```

I suppose it could be made to work with multiple assignments in a `local` declaration, but it’s parsed slightly differently from a `let`. E.g. `let i = 1, 2` does not create an `i == (1, 2)`.

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [July 29, 2025, 10:51am UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/17 "2025-07-29T10:51:40Z")

</div>

> [@\_bernhard](#):
>
> only valid as part of a `let` block.

Since the head line of `let` is essentially a `local` declaration, why are they differently designed?

> [@sgaure](#):
>
> `local i, j, k`

`let i, j, k` has a similar sense, e.g. this example

```julia
julia> for _ = 1:1
           a, b, c, x, y, z = 1, 2, 3, 7, 8, 9
           let z, y, x
               a, b, c, x, y, z = -1, -2, -3, -7, -8, -9
           end
           println("$a, $b, $c; $x, $y, $z")
       end
-1, -2, -3; 7, 8, 9

```

---

<div class="post-metadata">

**Author:** ![sgaure](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sgaure/32/14779_2.png) [@sgaure](https://discourse.julialang.org/u/sgaure)\
**Post date:** [July 29, 2025, 11:16am UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/18 "2025-07-29T11:16:38Z")

</div>

> [@WalterMadelim](#):
>
> Since the head line of `let` is essentially a `local` declaration, why are they differently designed?

I have no idea. I only need `let` and `local` occasionally, and have never given it any thought.

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [July 29, 2025, 11:18am UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/19 "2025-07-29T11:18:14Z")

</div>

> [@sgaure](#):
>
> That’s not exactly true.

In fact you could have said “That’s not true”.  
Here is an example:

```julia
julia> d = Dict(i => 10i for i = 1:5)
Dict{Int64, Int64} with 5 entries:
  5 => 50
  4 => 40
  2 => 20
  3 => 30
  1 => 10

julia> for (k, v) = collect(d)
           println("$k => $v")
           if k == 4
               empty!(d)
           end
           println(d)
       end
5 => 50
Dict(5 => 50, 4 => 40, 2 => 20, 3 => 30, 1 => 10)
4 => 40
Dict{Int64, Int64}()
2 => 20
Dict{Int64, Int64}()
3 => 30
Dict{Int64, Int64}()
1 => 10
Dict{Int64, Int64}()

```

We can infer that `collect(d)` is not re-evaluated.  
My finding was not correct—as you pointed out—it’s undefined behavior.

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [July 29, 2025, 11:25am UTC](https://discourse.julialang.org/t/cartesian-for-loop/130353/20 "2025-07-29T11:25:03Z")

</div>

> [@eldee](#):
>
> the code blocks you quoted are really equivalent

At that time, I was thinking

- The output sequence is a **complete** range, therefore I know that `for i = shuffle(1:9)` was evaluated **only once and then settled.**. Otherwise, if `for i = shuffle(1:9)` is dynamically updated in each iteration, then the chances that I can observe a **complete** range is very small.

[Next page](https://discourse.julialang.org/t/cartesian-for-loop/130353.md?page=2)
