Skip to content
Docs menu / Built-In Schedules

Built-In Schedules

To demonstrate the functionality of different schedules, we will use the following helper function that logs each repetition along with the corresponding delay in milliseconds, formatted as:

#<repetition>: <delay in ms>

Helper (Logging Execution Delays)

import { Array, Duration, Effect, Pull, Schedule } from "effect"
const log = (
schedule: Schedule.Schedule<unknown>,
delay: Duration.Input = 0,
): void => {
const maxRecurs = 10 // Limit the number of executions
const withDelay = Schedule.addDelay(schedule, () => Effect.succeed(delay))
const delays = Effect.runSync(
Effect.gen(function* () {
const step = yield* Schedule.toStep(withDelay)
const out: Array<Duration.Duration> = []
let now = Date.now()
for (const input of Array.range(0, maxRecurs)) {
const duration = yield* Pull.matchEffect(step(now, input), {
onSuccess: ([, duration]) => Effect.succeed(duration),
onFailure: Effect.failCause,
onDone: () => Effect.succeed(undefined),
})
if (duration === undefined) break
out.push(duration)
now += Duration.toMillis(duration)
}
return out
}),
)
delays.forEach((duration, i) => {
console.log(
i === maxRecurs
? "..." // Indicate truncation if there are more executions
: i === delays.length - 1
? "(end)" // Mark the last execution
: `#${i + 1}: ${Duration.toMillis(duration)}ms`,
)
})
}
typeof log // => "function"

Infinite and Fixed Repeats

forever

A schedule that repeats indefinitely, producing the number of recurrences each time it runs.

Example (Indefinitely Recurring Schedule)

import { Array, Duration, Effect, Pull, Schedule } from "effect"
31 collapsed lines
const log = (
schedule: Schedule.Schedule<unknown>,
delay: Duration.Input = 0,
): Array<Duration.Duration> => {
const maxRecurs = 10
const withDelay = Schedule.addDelay(schedule, () => Effect.succeed(delay))
const delays = Effect.runSync(
Effect.gen(function* () {
const step = yield* Schedule.toStep(withDelay)
const out: Array<Duration.Duration> = []
let now = Date.now()
for (const input of Array.range(0, maxRecurs)) {
const duration = yield* Pull.matchEffect(step(now, input), {
onSuccess: ([, duration]) => Effect.succeed(duration),
onFailure: Effect.failCause,
onDone: () => Effect.succeed(undefined),
})
if (duration === undefined) break
out.push(duration)
now += Duration.toMillis(duration)
}
return out
}),
)
delays.forEach((duration, i) => {
console.log(
i === maxRecurs
? "..."
: i === delays.length - 1
? "(end)"
: `#${i + 1}: ${Duration.toMillis(duration)}ms`,
)
})
return delays
}
const schedule = Schedule.forever
log(schedule)
/*
Output:
#1: 0ms < forever
#2: 0ms
#3: 0ms
#4: 0ms
#5: 0ms
#6: 0ms
#7: 0ms
#8: 0ms
#9: 0ms
#10: 0ms
...
*/
const result = log(schedule)
result.map(Duration.toMillis) // => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

once

A schedule that recurs only once.

Example (Single Recurrence Schedule)

import { Array, Duration, Effect, Pull, Schedule } from "effect"
31 collapsed lines
const log = (
schedule: Schedule.Schedule<unknown>,
delay: Duration.Input = 0,
): Array<Duration.Duration> => {
const maxRecurs = 10
const withDelay = Schedule.addDelay(schedule, () => Effect.succeed(delay))
const delays = Effect.runSync(
Effect.gen(function* () {
const step = yield* Schedule.toStep(withDelay)
const out: Array<Duration.Duration> = []
let now = Date.now()
for (const input of Array.range(0, maxRecurs)) {
const duration = yield* Pull.matchEffect(step(now, input), {
onSuccess: ([, duration]) => Effect.succeed(duration),
onFailure: Effect.failCause,
onDone: () => Effect.succeed(undefined),
})
if (duration === undefined) break
out.push(duration)
now += Duration.toMillis(duration)
}
return out
}),
)
delays.forEach((duration, i) => {
console.log(
i === maxRecurs
? "..."
: i === delays.length - 1
? "(end)"
: `#${i + 1}: ${Duration.toMillis(duration)}ms`,
)
})
return delays
}
const schedule = Schedule.duration(Duration.zero)
log(schedule)
/*
Output:
#1: 0ms < once
(end)
*/
const result = log(schedule)
result.map(Duration.toMillis) // => [0]

recurs

A schedule that repeats a specified number of times, producing the number of recurrences each time it runs.

Example (Fixed Number of Recurrences)

import { Array, Duration, Effect, Pull, Schedule } from "effect"
31 collapsed lines
const log = (
schedule: Schedule.Schedule<unknown>,
delay: Duration.Input = 0,
): Array<Duration.Duration> => {
const maxRecurs = 10
const withDelay = Schedule.addDelay(schedule, () => Effect.succeed(delay))
const delays = Effect.runSync(
Effect.gen(function* () {
const step = yield* Schedule.toStep(withDelay)
const out: Array<Duration.Duration> = []
let now = Date.now()
for (const input of Array.range(0, maxRecurs)) {
const duration = yield* Pull.matchEffect(step(now, input), {
onSuccess: ([, duration]) => Effect.succeed(duration),
onFailure: Effect.failCause,
onDone: () => Effect.succeed(undefined),
})
if (duration === undefined) break
out.push(duration)
now += Duration.toMillis(duration)
}
return out
}),
)
delays.forEach((duration, i) => {
console.log(
i === maxRecurs
? "..."
: i === delays.length - 1
? "(end)"
: `#${i + 1}: ${Duration.toMillis(duration)}ms`,
)
})
return delays
}
const schedule = Schedule.recurs(5)
log(schedule)
/*
Output:
#1: 0ms < recurs
#2: 0ms
#3: 0ms
#4: 0ms
#5: 0ms
(end)
*/
const result = log(schedule)
result.map(Duration.toMillis) // => [0, 0, 0, 0, 0]

Recurring at specific intervals

You can define schedules that control the time between executions. The difference between spaced and fixed schedules lies in how the interval is measured:

  • spaced delays each repetition from the end of the previous one.
  • fixed ensures repetitions occur at regular intervals, regardless of execution time.

spaced

A schedule that repeats indefinitely, each repetition spaced the specified duration from the last run. It returns the number of recurrences each time it runs.

Example (Recurring with Delay Between Executions)

import { Array, Duration, Effect, Pull, Schedule } from "effect"
31 collapsed lines
const log = (
schedule: Schedule.Schedule<unknown>,
delay: Duration.Input = 0,
): Array<Duration.Duration> => {
const maxRecurs = 10
const withDelay = Schedule.addDelay(schedule, () => Effect.succeed(delay))
const delays = Effect.runSync(
Effect.gen(function* () {
const step = yield* Schedule.toStep(withDelay)
const out: Array<Duration.Duration> = []
let now = Date.now()
for (const input of Array.range(0, maxRecurs)) {
const duration = yield* Pull.matchEffect(step(now, input), {
onSuccess: ([, duration]) => Effect.succeed(duration),
onFailure: Effect.failCause,
onDone: () => Effect.succeed(undefined),
})
if (duration === undefined) break
out.push(duration)
now += Duration.toMillis(duration)
}
return out
}),
)
delays.forEach((duration, i) => {
console.log(
i === maxRecurs
? "..."
: i === delays.length - 1
? "(end)"
: `#${i + 1}: ${Duration.toMillis(duration)}ms`,
)
})
return delays
}
const schedule = Schedule.spaced("200 millis")
// ┌─── Simulating an effect that takes
// │ 100 milliseconds to complete
// ▼
log(schedule, "100 millis")
/*
Output:
#1: 300ms < spaced
#2: 300ms
#3: 300ms
#4: 300ms
#5: 300ms
#6: 300ms
#7: 300ms
#8: 300ms
#9: 300ms
#10: 300ms
...
*/
const result = log(schedule, "100 millis")
result.map(Duration.toMillis) // => [300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300]

The first delay is approximately 100 milliseconds, as the initial execution is not affected by the schedule. Subsequent delays are approximately 200 milliseconds apart, demonstrating the effect of the spaced schedule.

fixed

A schedule that recurs at fixed intervals. It returns the number of recurrences each time it runs. If the action run between updates takes longer than the interval, then the action will be run immediately, but re-runs will not “pile up”.

|-----interval-----|-----interval-----|-----interval-----|
|---------action--------|action-------|action------------|

Example (Fixed Interval Recurrence)

import { Array, Duration, Effect, Pull, Schedule } from "effect"
31 collapsed lines
const log = (
schedule: Schedule.Schedule<unknown>,
delay: Duration.Input = 0,
): Array<Duration.Duration> => {
const maxRecurs = 10
const withDelay = Schedule.addDelay(schedule, () => Effect.succeed(delay))
const delays = Effect.runSync(
Effect.gen(function* () {
const step = yield* Schedule.toStep(withDelay)
const out: Array<Duration.Duration> = []
let now = Date.now()
for (const input of Array.range(0, maxRecurs)) {
const duration = yield* Pull.matchEffect(step(now, input), {
onSuccess: ([, duration]) => Effect.succeed(duration),
onFailure: Effect.failCause,
onDone: () => Effect.succeed(undefined),
})
if (duration === undefined) break
out.push(duration)
now += Duration.toMillis(duration)
}
return out
}),
)
delays.forEach((duration, i) => {
console.log(
i === maxRecurs
? "..."
: i === delays.length - 1
? "(end)"
: `#${i + 1}: ${Duration.toMillis(duration)}ms`,
)
})
return delays
}
const schedule = Schedule.fixed("200 millis")
// ┌─── Simulating an effect that takes
// │ 100 milliseconds to complete
// ▼
log(schedule, "100 millis")
/*
Output:
#1: 300ms < fixed
#2: 200ms
#3: 200ms
#4: 200ms
#5: 200ms
#6: 200ms
#7: 200ms
#8: 200ms
#9: 200ms
#10: 200ms
...
*/
const result = log(schedule, "100 millis")
result.map(Duration.toMillis) // => [300, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200]

Increasing Delays Between Executions

exponential

A schedule that recurs using exponential backoff, with each delay increasing exponentially. Returns the current duration between recurrences.

Example (Exponential Backoff Schedule)

import { Array, Duration, Effect, Pull, Schedule } from "effect"
31 collapsed lines
const log = (
schedule: Schedule.Schedule<unknown>,
delay: Duration.Input = 0,
): Array<Duration.Duration> => {
const maxRecurs = 10
const withDelay = Schedule.addDelay(schedule, () => Effect.succeed(delay))
const delays = Effect.runSync(
Effect.gen(function* () {
const step = yield* Schedule.toStep(withDelay)
const out: Array<Duration.Duration> = []
let now = Date.now()
for (const input of Array.range(0, maxRecurs)) {
const duration = yield* Pull.matchEffect(step(now, input), {
onSuccess: ([, duration]) => Effect.succeed(duration),
onFailure: Effect.failCause,
onDone: () => Effect.succeed(undefined),
})
if (duration === undefined) break
out.push(duration)
now += Duration.toMillis(duration)
}
return out
}),
)
delays.forEach((duration, i) => {
console.log(
i === maxRecurs
? "..."
: i === delays.length - 1
? "(end)"
: `#${i + 1}: ${Duration.toMillis(duration)}ms`,
)
})
return delays
}
const schedule = Schedule.exponential("10 millis")
log(schedule)
/*
Output:
#1: 10ms < exponential
#2: 20ms
#3: 40ms
#4: 80ms
#5: 160ms
#6: 320ms
#7: 640ms
#8: 1280ms
#9: 2560ms
#10: 5120ms
...
*/
const result = log(schedule)
result.map(Duration.toMillis) // => [10, 20, 40, 80, 160, 320, 640, 1280, 2560, 5120, 10240]

fibonacci

A schedule that always recurs, increasing delays by summing the preceding two delays (similar to the fibonacci sequence). Returns the current duration between recurrences.

Example (Fibonacci Delay Schedule)

import { Array, Duration, Effect, Pull, Schedule } from "effect"
31 collapsed lines
const log = (
schedule: Schedule.Schedule<unknown>,
delay: Duration.Input = 0,
): Array<Duration.Duration> => {
const maxRecurs = 10
const withDelay = Schedule.addDelay(schedule, () => Effect.succeed(delay))
const delays = Effect.runSync(
Effect.gen(function* () {
const step = yield* Schedule.toStep(withDelay)
const out: Array<Duration.Duration> = []
let now = Date.now()
for (const input of Array.range(0, maxRecurs)) {
const duration = yield* Pull.matchEffect(step(now, input), {
onSuccess: ([, duration]) => Effect.succeed(duration),
onFailure: Effect.failCause,
onDone: () => Effect.succeed(undefined),
})
if (duration === undefined) break
out.push(duration)
now += Duration.toMillis(duration)
}
return out
}),
)
delays.forEach((duration, i) => {
console.log(
i === maxRecurs
? "..."
: i === delays.length - 1
? "(end)"
: `#${i + 1}: ${Duration.toMillis(duration)}ms`,
)
})
return delays
}
const schedule = Schedule.fibonacci("10 millis")
log(schedule)
/*
Output:
#1: 10ms
#2: 20ms
#3: 30ms
#4: 50ms
#5: 80ms
#6: 130ms
#7: 210ms
#8: 340ms
#9: 550ms
#10: 890ms
...
*/
const result = log(schedule)
result.map(Duration.toMillis) // => [10, 20, 30, 50, 80, 130, 210, 340, 550, 890, 1440]