# Broadcast StepRange

**URL:** https://discourse.julialang.org/t/broadcast-steprange/134867
**Category:** General Usage
**Created:** [January 4, 2026, 1:50pm UTC](https://discourse.julialang.org/t/broadcast-steprange/134867 "2026-01-04T13:50:21Z")
**Posts on this page:** 4
**Page:** 1

<div class="post-metadata">

### Author: ![freeman](https://avatars.discourse-cdn.com/v4/letter/f/ec9cab/32.png) [@freeman](https://discourse.julialang.org/u/freeman)
#### Post date: [January 4, 2026, 1:50pm UTC](https://discourse.julialang.org/t/broadcast-steprange/134867/1 "2026-01-04T13:50:21Z")

</div>

Consider

```julia-auto
times_dt = DateTime("2025-12-29T00:18:00"):Minute(1):DateTime("2025-12-29T22:21:00")
typeof(times_dt) # StepRange{DateTime, Minute}

```

Consider broading on this.

```julia-auto
DateTime.(times_dt) |> typeof # Vector{DateTime}

```

My first question is: is this intentional or overlooked? If it’s intentional, is it intentional because there’s the expectation that the return type `broadcast(func, x)` is always type `Vector{something}`? If we don’t have that constraint on the return type of a broadcast, should we specialize `broadcast(DateTime, x::StepRange{DateTime, thing}) = x`?

There’s perhaps a wrinkle here. I tried:

```julia-auto
Base.broadcast(DateTime, x::StepRange{DateTime,S}) where {S} = x
DateTime.(times_dt) |> typeof # Vector{DateTime}
broadcast(DateTime, times_dt) |> typeof # StepRange{DateTime, Minute}

```

I thought that the dot syntax was syntactic sugar for calling `Base.broadcast` but apparently that’s not the case…? This doc [1] confirms my expectation, but that’s not what we see here…?

> More generally, `f.(args...)` is actually equivalent to `broadcast(f, args...)`

[1] [Functions · The Julia Language](https://docs.julialang.org/en/v1/manual/functions/#man-vectorized)

---

<div class="post-metadata">

### Author: ![mcabbott](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mcabbott/32/6603_2.png) [@mcabbott](https://discourse.julialang.org/u/mcabbott)
#### Post date: [January 4, 2026, 3:46pm UTC](https://discourse.julialang.org/t/broadcast-steprange/134867/2 "2026-01-04T15:46:13Z")

</div>

There are a few broadcasts over ranges which specialise to return another range, but not many. I don’t think there’s a very precise rule:

```julia-auto
julia> 2 .* (1:3) # also 2 * (1:3)
2:2:6

julia> (1:3) .+ (10:12.0) # also (1:3) + (10:12.0)
11.0:2.0:15.0

julia> float.(1:3) # but float(1:3) is a range
3-element Vector{Float64}:
 1.0
 2.0
 3.0

julia> Int.(1:3) # trivial
3-element Vector{Int64}:
 1
 2
 3

```

The last one, like yours, broadcasts a type which matches the `eltype`, and could simply do nothing.

The dot and `broadcast` are equivalent in that they call the same functions. The special cases above overload `broadcasted`:

```julia-auto
julia> using Dates

julia> times_dt = DateTime("2025-12-29T00:18:00"):Minute(1):DateTime("2025-12-29T22:21:00")
DateTime("2025-12-29T00:18:00"):Minute(1):DateTime("2025-12-29T22:21:00")

julia> Meta.@lower DateTime.(times_dt)
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope`
1 ─ %1 = Base.broadcasted(DateTime, times_dt)
│ %2 = Base.materialize(%1)
└── return %2
))))

julia> @less broadcast(DateTime, times_dt)
# broadcast(f::Tf, As...) where {Tf} = materialize(broadcasted(f, As...))

julia> Broadcast.broadcasted(*, 2, 1:3)
2:2:6

```

---

<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: [January 4, 2026, 4:49pm UTC](https://discourse.julialang.org/t/broadcast-steprange/134867/3 "2026-01-04T16:49:55Z")

</div>

> [@freeman](#):
>
> is it intentional because there’s the expectation that the return type `broadcast(func, x)` is always type `Vector{something}`

That’s just the default, and as mentioned already, there are custom implementations. One possibility is changing the general output array, but there’s also [function-specific](https://docs.julialang.org/en/v1/manual/interfaces/#extending-in-place-broadcast) implementations. With ranges, it’s obvious that some functions wouldn’t preserve a constant step:

```julia-auto
julia> (1:3) .* 2
2:2:6

julia> (1:3) .^ 2
3-element Vector{Int64}:
 1
 4
 9

```

> [@freeman](#):
>
> I thought that the dot syntax was syntactic sugar for calling `Base.broadcast`

They’re intended to be equivalent, but that doesn’t imply one is syntactic sugar for the other. You can check `Meta.@lower` for what syntactic sugar really does, and it’s allowed to vary across versions.

> [@freeman](#):
>
> `Base.broadcast(DateTime, x::StepRange{DateTime,S}) where {S} = x`

This `identity`-like implementation is not how broadcasting is intended to be customized, so it broke the equivalence. In general, you can’t assume you can extend a given function just by writing another method for it, you need to learn the API.

As for your specific example, it’s technically possible for broadcasted type constructors to just copy (not `identity`, that’s really bad for mutables), but I don’t think there’s much precedence for it, even in the simplest examples:

```julia-auto
julia> Int.(1:3)
3-element Vector{Int64}:
 1
 2
 3

```

---

<div class="post-metadata">

### Author: ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)
#### Post date: [January 5, 2026, 12:23am UTC](https://discourse.julialang.org/t/broadcast-steprange/134867/4 "2026-01-05T00:23:01Z")

</div>

Unfortunately, it’s hit or miss whether some operation on a range gives a range back. Moreover, it’s inconsistent between broadcast vs `map`: some operations preserve range when you broadcast them `f.(xs)` (but not when `map(f, xs)`), some behave the other way. See an old issue about that at [range map-broadcast inconsistency · Issue #54364 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/54364).
