Concurrency Options

On this page

Effect offers various options to manage how effects are executed and control the overall operation's result. These options help determine how many effects can run at the same time concurrently.

ts
type Options = {
readonly concurrency?: Concurrency
/* ... other options ... */
}
ts
type Options = {
readonly concurrency?: Concurrency
/* ... other options ... */
}

In this section, we'll focus on the option that handles concurrency, which is the concurrency option with a type of Concurrency:

ts
type Concurrency = number | "unbounded" | "inherit"
ts
type Concurrency = number | "unbounded" | "inherit"

Let's understand the meaning of each configuration value.

The following examples use the Effect.all function, but the concept applies to many other Effect APIs that accept concurrency options, such as Effect.forEach.

Sequential Execution (Default)

By default, if you don't specify any concurrency option, effects will run sequentially, one after the other. This means each effect starts only after the previous one completes.

ts
import { Effect, Duration } from "effect"
 
const makeTask = (n: number, delay: Duration.DurationInput) =>
Effect.promise(
() =>
new Promise<void>((resolve) => {
console.log(`start task${n}`)
setTimeout(() => {
console.log(`task${n} done`)
resolve()
}, Duration.toMillis(delay))
})
)
 
const task1 = makeTask(1, "200 millis")
const task2 = makeTask(2, "100 millis")
 
const sequential = Effect.all([task1, task2])
 
Effect.runPromise(sequential)
/*
Output:
start task1
task1 done
start task2 <-- task2 starts only after task1 completes
task2 done
*/
ts
import { Effect, Duration } from "effect"
 
const makeTask = (n: number, delay: Duration.DurationInput) =>
Effect.promise(
() =>
new Promise<void>((resolve) => {
console.log(`start task${n}`)
setTimeout(() => {
console.log(`task${n} done`)
resolve()
}, Duration.toMillis(delay))
})
)
 
const task1 = makeTask(1, "200 millis")
const task2 = makeTask(2, "100 millis")
 
const sequential = Effect.all([task1, task2])
 
Effect.runPromise(sequential)
/*
Output:
start task1
task1 done
start task2 <-- task2 starts only after task1 completes
task2 done
*/

Numbered Concurrency

You can control the number of concurrent operations with the concurrency option. For example, with concurrency: 2, up to 2 effects will run simultaneously.

ts
import { Effect, Duration } from "effect"
 
const makeTask = (n: number, delay: Duration.DurationInput) =>
Effect.promise(
() =>
new Promise<void>((resolve) => {
console.log(`start task${n}`)
setTimeout(() => {
console.log(`task${n} done`)
resolve()
}, Duration.toMillis(delay))
})
)
 
const task1 = makeTask(1, "200 millis")
const task2 = makeTask(2, "100 millis")
const task3 = makeTask(3, "210 millis")
const task4 = makeTask(4, "110 millis")
const task5 = makeTask(5, "150 millis")
 
const number = Effect.all([task1, task2, task3, task4, task5], {
concurrency: 2
})
 
Effect.runPromise(number)
/*
Output:
start task1
start task2 <-- active tasks: task1, task2
task2 done
start task3 <-- active tasks: task1, task3
task1 done
start task4 <-- active tasks: task3, task4
task4 done
start task5 <-- active tasks: task3, task5
task3 done
task5 done
*/
ts
import { Effect, Duration } from "effect"
 
const makeTask = (n: number, delay: Duration.DurationInput) =>
Effect.promise(
() =>
new Promise<void>((resolve) => {
console.log(`start task${n}`)
setTimeout(() => {
console.log(`task${n} done`)
resolve()
}, Duration.toMillis(delay))
})
)
 
const task1 = makeTask(1, "200 millis")
const task2 = makeTask(2, "100 millis")
const task3 = makeTask(3, "210 millis")
const task4 = makeTask(4, "110 millis")
const task5 = makeTask(5, "150 millis")
 
const number = Effect.all([task1, task2, task3, task4, task5], {
concurrency: 2
})
 
Effect.runPromise(number)
/*
Output:
start task1
start task2 <-- active tasks: task1, task2
task2 done
start task3 <-- active tasks: task1, task3
task1 done
start task4 <-- active tasks: task3, task4
task4 done
start task5 <-- active tasks: task3, task5
task3 done
task5 done
*/

Unbounded Concurrency

If you set concurrency: "unbounded", as many effects as needed will run concurrently, without any specific limit.

ts
import { Effect, Duration } from "effect"
 
const makeTask = (n: number, delay: Duration.DurationInput) =>
Effect.promise(
() =>
new Promise<void>((resolve) => {
console.log(`start task${n}`)
setTimeout(() => {
console.log(`task${n} done`)
resolve()
}, Duration.toMillis(delay))
})
)
 
const task1 = makeTask(1, "200 millis")
const task2 = makeTask(2, "100 millis")
const task3 = makeTask(3, "210 millis")
const task4 = makeTask(4, "110 millis")
const task5 = makeTask(5, "150 millis")
 
const unbounded = Effect.all([task1, task2, task3, task4, task5], {
concurrency: "unbounded"
})
 
Effect.runPromise(unbounded)
/*
Output:
start task1
start task2
start task3
start task4
start task5
task2 done
task4 done
task5 done
task1 done
task3 done
*/
ts
import { Effect, Duration } from "effect"
 
const makeTask = (n: number, delay: Duration.DurationInput) =>
Effect.promise(
() =>
new Promise<void>((resolve) => {
console.log(`start task${n}`)
setTimeout(() => {
console.log(`task${n} done`)
resolve()
}, Duration.toMillis(delay))
})
)
 
const task1 = makeTask(1, "200 millis")
const task2 = makeTask(2, "100 millis")
const task3 = makeTask(3, "210 millis")
const task4 = makeTask(4, "110 millis")
const task5 = makeTask(5, "150 millis")
 
const unbounded = Effect.all([task1, task2, task3, task4, task5], {
concurrency: "unbounded"
})
 
Effect.runPromise(unbounded)
/*
Output:
start task1
start task2
start task3
start task4
start task5
task2 done
task4 done
task5 done
task1 done
task3 done
*/

Inherit Concurrency

The concurrency: "inherit" option adapts based on context, controlled by Effect.withConcurrency(number | "unbounded").

If there's no Effect.withConcurrency call, the default is "unbounded". Otherwise, it inherits the configuration set by Effect.withConcurrency.

ts
import { Effect, Duration } from "effect"
 
const makeTask = (n: number, delay: Duration.DurationInput) =>
Effect.promise(
() =>
new Promise<void>((resolve) => {
console.log(`start task${n}`)
setTimeout(() => {
console.log(`task${n} done`)
resolve()
}, Duration.toMillis(delay))
})
)
 
const task1 = makeTask(1, "200 millis")
const task2 = makeTask(2, "100 millis")
const task3 = makeTask(3, "210 millis")
const task4 = makeTask(4, "110 millis")
const task5 = makeTask(5, "150 millis")
 
const inherit = Effect.all([task1, task2, task3, task4, task5], {
concurrency: "inherit"
})
 
Effect.runPromise(inherit)
/*
Output:
start task1
start task2
start task3
start task4
start task5
task2 done
task4 done
task5 done
task1 done
task3 done
*/
ts
import { Effect, Duration } from "effect"
 
const makeTask = (n: number, delay: Duration.DurationInput) =>
Effect.promise(
() =>
new Promise<void>((resolve) => {
console.log(`start task${n}`)
setTimeout(() => {
console.log(`task${n} done`)
resolve()
}, Duration.toMillis(delay))
})
)
 
const task1 = makeTask(1, "200 millis")
const task2 = makeTask(2, "100 millis")
const task3 = makeTask(3, "210 millis")
const task4 = makeTask(4, "110 millis")
const task5 = makeTask(5, "150 millis")
 
const inherit = Effect.all([task1, task2, task3, task4, task5], {
concurrency: "inherit"
})
 
Effect.runPromise(inherit)
/*
Output:
start task1
start task2
start task3
start task4
start task5
task2 done
task4 done
task5 done
task1 done
task3 done
*/

If you use Effect.withConcurrency, it will adopt that specific concurrency configuration.

ts
import { Effect, Duration } from "effect"
 
const makeTask = (n: number, delay: Duration.DurationInput) =>
Effect.promise(
() =>
new Promise<void>((resolve) => {
console.log(`start task${n}`)
setTimeout(() => {
console.log(`task${n} done`)
resolve()
}, Duration.toMillis(delay))
})
)
 
const task1 = makeTask(1, "200 millis")
const task2 = makeTask(2, "100 millis")
const task3 = makeTask(3, "210 millis")
const task4 = makeTask(4, "110 millis")
const task5 = makeTask(5, "150 millis")
 
const inherit = Effect.all([task1, task2, task3, task4, task5], {
concurrency: "inherit"
})
 
const withConcurrency = inherit.pipe(Effect.withConcurrency(2))
 
Effect.runPromise(withConcurrency)
/*
Output:
start task1
start task2 <-- active tasks: task1, task2
task2 done
start task3 <-- active tasks: task1, task3
task1 done
start task4 <-- active tasks: task3, task4
task4 done
start task5 <-- active tasks: task3, task5
task3 done
task5 done
*/
ts
import { Effect, Duration } from "effect"
 
const makeTask = (n: number, delay: Duration.DurationInput) =>
Effect.promise(
() =>
new Promise<void>((resolve) => {
console.log(`start task${n}`)
setTimeout(() => {
console.log(`task${n} done`)
resolve()
}, Duration.toMillis(delay))
})
)
 
const task1 = makeTask(1, "200 millis")
const task2 = makeTask(2, "100 millis")
const task3 = makeTask(3, "210 millis")
const task4 = makeTask(4, "110 millis")
const task5 = makeTask(5, "150 millis")
 
const inherit = Effect.all([task1, task2, task3, task4, task5], {
concurrency: "inherit"
})
 
const withConcurrency = inherit.pipe(Effect.withConcurrency(2))
 
Effect.runPromise(withConcurrency)
/*
Output:
start task1
start task2 <-- active tasks: task1, task2
task2 done
start task3 <-- active tasks: task1, task3
task1 done
start task4 <-- active tasks: task3, task4
task4 done
start task5 <-- active tasks: task3, task5
task3 done
task5 done
*/