# Why won't julia ERROR if a variable is not defined when scheduling?

**URL:** https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438
**Category:** New to Julia
**Tags:** question, task
**Created:** [September 16, 2025, 12:27pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438 "2025-09-16T12:27:43Z")
**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: [September 16, 2025, 12:27pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/1 "2025-09-16T12:27:43Z")

</div>

I’m very confused by this behavior:

```julia
# start a new julia REPL
julia> function work(b)
           sleep(3)
           println(b)
       end;

julia> function main()
           t = Task(() -> work(c))
           schedule(t)
           c = 9
           wait(t)
       end;

julia> main()
9

```

You see, `c` is not defined when `schedule(t)`. I expect an `ERROR` when `schedule(t)`, am I right?

* * *

This comparative test is even more puzzling

```julia
julia> function work(b)
           sleep(3)
           println(b)
       end;

julia> function main()
           t = Task(() -> work(c))
           schedule(t)
           println("I merely insert one line here")
           c = 9
           wait(t)
       end;

julia> main()
I merely insert one line here
ERROR: TaskFailedException
Stacktrace:
 [1] wait(t::Task)
   @ Base ./task.jl:370
 [2] main()
   @ Main ./REPL[2]:6
 [3] top-level scope
   @ REPL[3]:1

    nested task error: UndefVarError: `c` not defined in local scope
    Suggestion: check for an assignment to a local variable that shadows a global of the same name.
    Stacktrace:
     [1] (::var"#1#2")()
       @ Main ./REPL[2]:2

julia> Threads.nthreads(:default), Threads.nthreads(:interactive)
(255, 1)

julia> versioninfo()
Julia Version 1.11.6

```

---

<div class="post-metadata">

### Author: ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)
#### Post date: [September 16, 2025, 12:43pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/2 "2025-09-16T12:43:37Z")

</div>

There are no promises about when `() -> work(c)` will be called. Doing `schedule(t)` only schedules the task but it might not actually start for a while, so whether or not it crashes depends on whether it runs before or after `c` gets defined.

In your first case, `c` is defined immediately after `schedule(t)`, so will usually be defined before the task actually starts, so no error occurs.

In the second case, doing some IO usually ends up switching between tasks because IO is slow - it can do some other work while the IO is happening. This means that the task actually got started before `c` was defined, hence the error.

---

<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: [September 16, 2025, 1:22pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/3 "2025-09-16T13:22:47Z")

</div>

The `schedule(t)` only does a `push!(workqueue, t)` and returns, where `workqueue` is an internal queue of tasks belonging to the scheduler. The next time the scheduler activates the task is found and run. This typically happens when the current task enters some wait state, like I/O, `sleep`, `yield`, `wait`, `lock` or similar (It’s really not specified when this can happen, I think the doc states that it can happen at any time).

Anyway, when a task throws an error, the exception is just stored in its task struct before it terminates, and rethrown when a `fetch` or `wait` is performed. So you will not see it until a `wait` or `fetch`.

---

<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: [September 16, 2025, 1:35pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/4 "2025-09-16T13:35:02Z")

</div>

My meaning was, the arg to the `work` function should be decided when the task `t` is scheduled. Otherwise it’s super weird—I have no idea what `c` really is, i.e. I lost my mind about what I write becomes in the end.

For instance

```julia
task_vec = []
for i = [10, 20, 30]
    for j = i+1:i+5
        t = Task(() -> work(j))
        schedule(t)
        push!(task_vec, t)
        println("i = $i, j = $j")
    end
end

```

If `j` is cut-and-dried when `schedule(t)`, then I really know what my code is doing about.  
Otherwise, when the task defined at `i = 10` is started when `i = 20`, then the previous `j` is lost, no?

---

<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: [September 16, 2025, 1:37pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/5 "2025-09-16T13:37:16Z")

</div>

There is also the additional point that you’re (sometimes) allowed to reference variables before (in terms of location in code) they are declared, which is not related to multi-threading. E.g.

```julia-repl
julia> function main()
           inc_c() = c += 1 # What is c at this point?
           c = 1
           inc_c()
           println(c)
       end;

julia> main()
2

```

(See also [Is it okay that, in Julia, code that never executes can change the result of programs? - #21 by frankwswang](https://discourse.julialang.org/t/is-it-okay-that-in-julia-code-that-never-executes-can-change-the-result-of-programs/124195/21))

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [September 16, 2025, 1:40pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/6 "2025-09-16T13:40:17Z")

</div>

> [@eldee](#):
>
> you’re (sometimes) allowed to reference variables before (in terms of location in code) they are declared

From experience I’d say all variables are implicitly declared at the start of the narrowest enclosing scope.

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [September 16, 2025, 1:44pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/7 "2025-09-16T13:44:17Z")

</div>

> [@WalterMadelim](#):
>
> My meaning was, the arg to the `work` function should be decided when the task `t` is scheduled. Otherwise it’s super weird—I have no idea what `c` really is, i.e. I lost my mind about what I write becomes in the end.

`c` is a local variable in `main`. The closure `() -> work(c)` captures `c`. In Julia, capturing a variable does not make an implicit copy. The time at which a `schedule(task)` call happens is not relevant.

---

<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: [September 16, 2025, 1:47pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/8 "2025-09-16T13:47:45Z")

</div>

> [@WalterMadelim](#):
>
> ```julia-auto
> task_vec = []
> for i = [10, 20, 30]
> for j = i+1:i+5
> t = Task(() -> work(j))
> schedule(t)
> push!(task_vec, t)
> println("i = $i, j = $j")
> end
> end
> 
> ```
> 
> If `j` is cut-and-dried when `schedule(t)`, then I really know what my code is doing about.  
> Otherwise, when the task defined at `i = 10` is started when `i = 20`, then the previous `j` is lost, no?

This is a different situation. The task contains the closure `() -> work(j)` where `j` is assigned anew for every iteration. This closure is really a callable immutable struct with one field `j` which is set when the closure is created. Like

```julia-auto
struct uniquename
    j::Int
end
(s::uniquename)() = work(s.j)
...
t = Task(uniquename(j))

```

In the former case, the `c` becomes a untyped “boxed” variable, which initially is undefined, but which can change later (which it does). If it happens to be defined before it’s used in the task, everything will be fine.

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [September 16, 2025, 1:47pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/9 "2025-09-16T13:47:50Z")

</div>

> [@WalterMadelim](#):
>
> the arg to the `work` function should be decided when the task `t` is scheduled

What you can do is create a new task using `j`, instead of capturing `j`. For example:

```julia
let j = j
    Task(() -> work(j))
end

```

This still captures _a_ `j`, but not _the_ (outer) `j`. And the capture is not mutable/boxed, because the (inner) `j` is clearly never mutated.

---

<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: [September 16, 2025, 1:56pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/10 "2025-09-16T13:56:57Z")

</div>

If you do a `@code_warntype` on your original `main`, you’ll see the boxing of the `c` variable:

```julia-auto
ulia> @code_warntype main()
MethodInstance for main()
  from main() @ Main REPL[47]:1
Arguments
  #self#::Core.Const(Main.main)
Locals
  #35::var"#35#36"
  c::Core.Box
  t::Task
...

```

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [September 16, 2025, 2:01pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/11 "2025-09-16T14:01:24Z")

</div>

Great, now who’s going to explain why boxing happens 😆

Basically, Julia’s frontend boxes all local variables that get captured by a closure and may get reassigned. In other words, to be able to support reassignment of local variables that get captured, Julia’s frontend changes the type of the local variable to a mutable type with a single mutable field, so, thereafter, Julia just sees mutation, not reassignment.

Somewhat discussed in this issue:

- [https://github.com/JuliaLang/julia/issues/15276](https://github.com/JuliaLang/julia/issues/15276)

Probably someone should make a doc PR.

---

<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: [September 16, 2025, 2:21pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/12 "2025-09-16T14:21:59Z")

</div>

> [@sgaure](#):
>
> This is a different situation. The task contains the closure `() -> work(j)` where `j` is assigned anew for every iteration. This closure is really a callable immutable struct with one field `j` which is set when the closure is created. Like
> 
> ```julia-auto
> struct uniquename
> j::Int
> end
> (s::uniquename)() = work(s.j)
> ...
> t = Task(uniquename(j))
> 
> ```

I can somewhat understand what your code is doing. But I cannot get your overall meaning. (Since I haven’t write any packages, I have almost zero experience about `Struct`, `Module` etc. Because as a user I don’t need to define them by myself).

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [September 16, 2025, 2:27pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/13 "2025-09-16T14:27:49Z")

</div>

> [@WalterMadelim](#):
>
> But I cannot get your overall meaning.

Some context to what @sgaure is saying:

- the built-in syntax for functions in Julia is just syntax sugar for:

- in Julia, any type can be callable, just add a method

- in Julia, new types can be `struct` or `mutable struct` (or `abstract type`, but obviously those don’t have instances, so they are not relevant here)

TBH I’m not completely sure what @sgaure’s point was either. I think they’re saying that some closures get lowered to a `mutable struct`, but I’m not sure if that’s true.

---

<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: [September 16, 2025, 2:38pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/14 "2025-09-16T14:38:21Z")

</div>

Well, you made me discover something interesting.

I though `for` and `let` are somewhat alike since they both introduce a local scope.

But now I believe `let` is simpler thus superior, because it has no `Union` that `for` has:

```julia
julia> function test_local_scope()
           let k = 1
           end
       end;

julia> @code_warntype test_local_scope()

Locals
  k::Int64

julia> function test_local_scope()
           for k = 1
           end
       end;

julia> @code_warntype test_local_scope()

Locals
  @_2::Union{Nothing, Tuple{Int64, Nothing}}

```

As you can see, there is a yellow-color `Union` associated with `for`, which is not the case with `let`.

---

<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: [September 16, 2025, 2:58pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/15 "2025-09-16T14:58:30Z")

</div>

The intent of `for` is for repeated execution of code. The intent of `let` is to create a scope. Make your code more readable and maintainable by using the language constructs for their intended purpose, rather than for their side effects. Seeing a `for` loop abused as you do above will likely make most readers wonder at the point of it.

---

<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: [September 16, 2025, 3:06pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/16 "2025-09-16T15:06:55Z")

</div>

You’re right.

But my question is still

```julia
function work(b)
    sleep(2)
    println(b)
end

for i = [10, 20, 30]
    for j = i+1:i+4
        t = Task(() -> work(j))
        schedule(t)
    end
    sleep(1)
end

```

I “spawned” `3 * 4` tasks here.  
The first 3 tasks are scheduled when `i = 10`, surely.  
But the first 3 tasks might be executed at the time instant when `i == 20 or i == 30`, therefore the `j` is no longer the old ones, e.g. `j` is `21, 22, 23, 24` now.  
Will the first 3 tasks still use the old `j`’s, i.e. `11, 12, 13, 14`? Why is this guaranteed?

* * *

Imagine it is expanded as

```julia
i = 10
for j = [11, 12, 13, 14]
    body1(j)
end
i = 20
for j = [21, 22, 23, 24]
    body2(j)
end
i = 30
for j = [31, 32, 33, 34]
    body3(j)
end

```

Is the `j` serving as the arg to `body1` the same thing as the `j` serving as the arg to `body2`?  
I’m really confused.

---

<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: [September 16, 2025, 3:20pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/17 "2025-09-16T15:20:42Z")

</div>

> [@WalterMadelim](#):
>
> Will the first 3 tasks still use the old `j`’s, i.e. `11, 12, 13, 14`? Why is this guaranteed?

I can’t find it in the docs right now, but the loop variable is bound anew for every iteration, i.e.

```julia-auto
for i in 1:N
    ...
end

```

is just like

```julia-auto
for i in 1:N
    let i=i
        ...
    end
end

```

Btw, the reason a for loop always involves a union with `Nothing` is because it’s actually rewritten:

```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

```

> **[Interfaces · The Julia Language](https://docs.julialang.org/en/v1/manual/interfaces/#man-interface-iteration)**
>
> Documentation for The Julia Language.

And `iterate` will return `nothing` when iteration is finished.

---

<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: [September 16, 2025, 3:51pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/18 "2025-09-16T15:51:51Z")

</div>

> [@sgaure](#):
>
> I can’t find it in the docs right now, but the loop variable is bound anew for every iteration, i.e.
> 
> ```julia-auto
> for i in 1:N
> ...
> end
> 
> ```
> 
> is just like
> 
> ```julia-auto
> for i in 1:N
> let i=i
> ...
> end
> end
> 
> ```

Thanks, this description is very clear and reassuring.

It would be better if anyone could share a link where this statement is proved.

---

<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: [September 16, 2025, 4:05pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/19 "2025-09-16T16:05:15Z")

</div>

I have a proposition, based on the discussions above

- the following line 1 and line 2 are completely equivalent operations

```julia
... some background
v = rand(8)
i = 7
for j = view(v, 1:i) Threads.@spawn work(j) end # line 1
for ȷ = i:i Threads.@spawn work($(v[ȷ])) end # line 2
... some background

```

If no disagreement, I think this topic is coming to an end. 🙂

---

<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: [September 16, 2025, 11:09pm UTC](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438/20 "2025-09-16T23:09:59Z")

</div>

I’ve gained some deeper understanding (I hope so):

Taking this as an example

```julia
julia> function main()
           for j = RANGE Threads.@spawn work( $(f(g(j))) ) end
       end;

julia> @code_lowered main()
...
2 ┄ 
│ %10 = Main.f
│ %11 = Main.g
│ %12 = j
│ %13 = (%11)(%12)
│ %14 = (%10)(%13) # [COMMENT] THIS IS a _SNAPSHOT_ OF `f(g(j))`
│ 230 = %14
│ %16 = Base.Threads.Task
│ %17 = Main.:(var"#3#4") # [COMMENT] THIS IS `work` !
│ %18 = 230
│ %19 = Core.typeof(%18)
│ %20 = Core.apply_type(%17, %19)
│ %21 = 230
│ #3 = %new(%20, %21)
│ %23 = #3
│ task = (%16)(%23) # [COMMENT] TASK CREATED
....

```

I can see that the consequence of using `$` is the cut-and-dried static variable `%14`, which is later again assigned to `%21` and `%23`. Therefore, the snapshot _value_ of `f(g(j))` is **already decided** when the respective `task` is **created**.

This is **not the case** for `work`, which is assigned to `%17` but is later **unused** , **except** in that type inference `Core.apply_type`. This suggest that `work` is volatile—if `work` mutates later, the executing of that respective `task` may use the mutated `work`.

[Next page](https://discourse.julialang.org/t/why-wont-julia-error-if-a-variable-is-not-defined-when-scheduling/132438.md?page=2)
