# Recommended idiom for collecting narrowest type

**URL:** <https://discourse.julialang.org/t/recommended-idiom-for-collecting-narrowest-type/136604>\
**Category:** General Usage\
**Tags:** question, collection, collections\
**Created:** [April 7, 2026, 12:14pm UTC](https://discourse.julialang.org/t/recommended-idiom-for-collecting-narrowest-type/136604 "2026-04-07T12:14:12Z")\
**Posts on this page:** 17\
**Page:** 1

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [April 7, 2026, 12:14pm UTC](https://discourse.julialang.org/t/recommended-idiom-for-collecting-narrowest-type/136604/1 "2026-04-07T12:14:12Z")

</div>

Suppose I have a `Vector` with an abstract element type. What is the recommended idiom for collecting the elements into a vector with the narrowest possible element type? MWE:

```julia
struct Foo{T}
    x::T
end
X = Foo[Foo(1), Foo(2)]
collect(X) # Vector{Foo}
collect(x for x in X) # Vector{Foo{Int}}

```

The last one works but feels a bit clunky.

(It is understood that the operation is not type stable; this is before calling a function barrier)

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [April 7, 2026, 12:21pm UTC](https://discourse.julialang.org/t/recommended-idiom-for-collecting-narrowest-type/136604/2 "2026-04-07T12:21:34Z")

</div>

`identity.(X)` or `map(identity, X)`?

---

<div class="post-metadata">

**Author:** ![nilshg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nilshg/32/2283_2.png) [@nilshg](https://discourse.julialang.org/u/nilshg)\
**Post date:** [April 7, 2026, 12:22pm UTC](https://discourse.julialang.org/t/recommended-idiom-for-collecting-narrowest-type/136604/3 "2026-04-07T12:22:30Z")

</div>

I second jules’ suggestion, but your last line could also be `[x for x in X]` instead of `collect`.

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [April 7, 2026, 12:25pm UTC](https://discourse.julialang.org/t/recommended-idiom-for-collecting-narrowest-type/136604/4 "2026-04-07T12:25:12Z")

</div>

Is it _documented_ which construct actually tries to find the narrowest type? Empirically it works, but what does the language guarantee?

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [April 7, 2026, 12:28pm UTC](https://discourse.julialang.org/t/recommended-idiom-for-collecting-narrowest-type/136604/5 "2026-04-07T12:28:52Z")

</div>

I don’t think it tries to find the narrowest type. For example:

```julia
julia> x = ["a", 2, :c]
3-element Vector{Any}:
  "a"
 2
  :c

julia> identity.(x)
3-element Vector{Any}:
  "a"
 2
  :c

```

Julia has some heuristics which Union types it will choose in such scenarios and when it falls back to `Any`.

---

<div class="post-metadata">

**Author:** ![adienes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/adienes/32/37459_2.png) [@adienes](https://discourse.julialang.org/u/adienes)\
**Post date:** [April 7, 2026, 12:35pm UTC](https://discourse.julialang.org/t/recommended-idiom-for-collecting-narrowest-type/136604/6 "2026-04-07T12:35:47Z")

</div>

also comprehensions promote weirdly. `[1, 2.0]` becomes a `Float64[]` when you may have wanted `Real`.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [April 7, 2026, 2:18pm UTC](https://discourse.julialang.org/t/recommended-idiom-for-collecting-narrowest-type/136604/7 "2026-04-07T14:18:48Z")

</div>

> [@Tamas\_Papp](#):
>
> What is the recommended idiom for collecting the elements into a vector with the narrowest possible element type?

Part of the problem is that what you want doesn’t seem uniquely defined.

For example, if you have `x = Any[1.0f0, 2.0]`, do you want `AbstractFloat[1.0f0, 2.0]` (the result of `map(identity, x)` or `[x for x in x]`, corresponding to `typejoin`), or do you want `Float64[1.0, 2.0]` (which is a lossless conversion, corresponding to calling `promote_type` on the types via `mapreduce(typeof, promote_type, x)`, and also what you would get from `[1.0f0, 2.0]`). Or do you want `Union{Float32,Float64}[1.0f0, 2.0]`, which is narrower than `AbstractFloat`?

What are you trying to accomplish? If it’s to improve performance, then you probably want a concrete type like `Float64` or at worst a union of two or three types — whereas a `typejoin` like `AbstractFloat[...]` may be as bad as `Any[...]` for performance.

> [@adienes](#):
>
> also comprehensions promote weirdly. `[1, 2.0]` becomes a `Float64[]` when you may have wanted `Real`.

That’s literally what `promote` does (via `promote_type(Int, Float64) === Float64`).

It’s the difference between [_promotion_](https://docs.julialang.org/en/v1/manual/conversion-and-promotion/#Promotion) (`promote` and `promote_type`) versus [`typejoin`](https://docs.julialang.org/en/v1/base/base/#Base.typejoin) (and `promote_typejoin`).

> [@Tamas\_Papp](#):
>
> Empirically it works, but what does the language guarantee?

You can do `mapreduce(typeof, typejoin, x)` versus `mapreduce(typeof, promote_type, x)`, depending on which behavior you want, if you want to be explicit.

It would be nice to explicitly document that `collect` on an iterator uses `typejoin` (this doesn’t seem to be in the [current `collect` docstring?](https://docs.julialang.org/en/v1/base/collections/#Base.collect-Tuple%7BAny%7D)), whereas array literals use `promote_type`. (`map` and comprehensions are already documented to behave like `collect` on iterators.)

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [April 7, 2026, 3:38pm UTC](https://discourse.julialang.org/t/recommended-idiom-for-collecting-narrowest-type/136604/8 "2026-04-07T15:38:21Z")

</div>

> [@stevengj](#):
>
> What are you trying to accomplish?

Catching non-concrete container eltypes before a function barrier. Conversion is fine, but so is throwing an error so that I can investigate. _In theory_ the computation is such that I can prove that all elements have the same type. MWE:

```julia
struct Foo{T}
    x::T
end
foos = [Foo(rand() < 0.5 ? rand('a':'z') : rand(0:20)) for _ in 1:100];
collect1(x) = isempty(x) ? Nothing[] : collect(typeof(first(x)), x)
# ^ this does what I want
foos_char = collect1(filter(f -> f.x isa Char, foos))
foos_int = collect1(filter(f -> f.x isa Int, foos))
do_something_with(foos_char, foos_int)

```

In the actual example the types are rather complex and I would rather not compute them if it is avoidable.

---

<div class="post-metadata">

**Author:** ![Deduction42](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/deduction42/32/9206_2.png) [@Deduction42](https://discourse.julialang.org/u/Deduction42)\
**Post date:** [April 7, 2026, 4:15pm UTC](https://discourse.julialang.org/t/recommended-idiom-for-collecting-narrowest-type/136604/9 "2026-04-07T16:15:35Z")

</div>

I actually had issues with this about three months ago; I got weird results with collect but that’s because I wanted it to promote instead of typejoin. Is there a way to specify promote behaviour when using a comprehension, or a map, or a collect?

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [April 7, 2026, 4:25pm UTC](https://discourse.julialang.org/t/recommended-idiom-for-collecting-narrowest-type/136604/10 "2026-04-07T16:25:11Z")

</div>

I’ve looooong wanted a better API/name/idiom for incremental widening, somewhat akin to BangBang.jl. There’s private functionality in base that allows you to specify the allocator that `collect` uses, and it’d be very cool for this to also allow you to explicitly specify how it chooses the next wider value (e.g., `promote`, `typejoin`, unions, or some mix thereof).

I actually think the basics are there for this, but it needs some significant API design work.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [April 7, 2026, 6:09pm UTC](https://discourse.julialang.org/t/recommended-idiom-for-collecting-narrowest-type/136604/11 "2026-04-07T18:09:02Z")

</div>

> [@Tamas\_Papp](#):
>
> _In theory_ the computation is such that I can prove that all elements have the same type.

In that case it shouldn’t matter whether you are doing `promote` or `typejoin`, since they are both the identity for homogeneous concrete types. You can just check whether the result of `map` or `collect` has a concrete eltype.

---

<div class="post-metadata">

**Author:** ![Stephen\_Vavasis](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stephen_vavasis/32/3389_2.png) [@Stephen\_Vavasis](https://discourse.julialang.org/u/Stephen_Vavasis)\
**Post date:** [April 8, 2026, 10:51pm UTC](https://discourse.julialang.org/t/recommended-idiom-for-collecting-narrowest-type/136604/12 "2026-04-08T22:51:47Z")

</div>

You may wish to read Jeff Bezanson’s response to a related issue in some code that I wrote:

[Peer inside an abstract parametric type · Issue #46047 · JuliaLang/julia](https://github.com/JuliaLang/julia/issues/46047)

---

<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:** [April 9, 2026, 8:18am UTC](https://discourse.julialang.org/t/recommended-idiom-for-collecting-narrowest-type/136604/13 "2026-04-09T08:18:44Z")

</div>

> [@Tamas\_Papp](#):
>
> Suppose I have a `Vector` with an abstract element type. What is the recommended idiom for collecting the elements into a vector with the narrowest possible element type?

How to collect an iterator, `X`, into an instance of `Array`, `Y`, such that `eltype(Y)` is based on the `typejoin` of the type of each value of `X`:

- Using Collects.jl, which improves upon the interface and implementation of `collect`:

- Using `collect`, if you want to avoid depending on Collects.jl for some reason:

> [@Tamas\_Papp](#):
>
> Is it _documented_ which construct actually tries to find the narrowest type? Empirically it works, but what does the language guarantee?

The documentation says:

> The element type of the returned array is based on the types of the values collected. However, if the iterator is empty then the element type of the returned (empty) array is determined by type inference.

So the docs do not specify whether types are combined with `typejoin` or with promotion.

In my opinion:

- It would be breaking for `Base` to change this behavior of `collect` regarding returned eltype.

- However, a package adding a method to `collect` could do this. IMO that would be a bad practice, but allowed according to the contract as specified in the docs.

In any case, there is Collects.jl, which:

- Generalizes the `collect` interface.

- Documents the behavior in detail.

In summary: just use Collects.jl if you are worried about `collect` for any reason. Its topic here on Discourse:

- [New package: Collects.jl - meant to improve upon and generalize the interface of collect](https://discourse.julialang.org/t/new-package-collects-jl-meant-to-improve-upon-and-generalize-the-interface-of-collect/129468)

> [@jules](#):
>
> Julia has some heuristics which Union types it will choose in such scenarios and when it falls back to `Any`.

That is just ~~`typejoin`~~ `promote_typejoin`.

> [@adienes](#):
>
> also comprehensions promote weirdly. `[1, 2.0]` becomes a `Float64[]` when you may have wanted `Real`.

While the syntax is similar, there are multiple distinct things colloquially referred to as “comprehension”. The form you refer to (`[1, 2.0]`) does promotion, while the topic here, as defined in the OP, is the form that does `typejoin`.

> [@Deduction42](#):
>
> Is there a way to specify promote behaviour when using a comprehension, or a map, or a collect?

> [@mbauman](#):
>
> better API/name/idiom for incremental widening, somewhat akin to [BangBang.jl](https://juliaregistries.github.io/General/packages/redirect_to_repo/BangBang). There’s private functionality in base that allows you to specify the allocator that `collect` uses, and it’d be very cool for this to also allow you to explicitly specify how it chooses the next wider value (e.g., `promote`, `typejoin`, unions, or some mix thereof).

@Deduction42 @mbauman creating an issue on the Collects.jl repo:

- [allow using promotion instead of `typejoin` · Issue #73 · JuliaCollections/Collects.jl · GitHub](https://github.com/JuliaCollections/Collects.jl/issues/73)

Feel free to provide more specific suggestions.

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [April 9, 2026, 9:12am UTC](https://discourse.julialang.org/t/recommended-idiom-for-collecting-narrowest-type/136604/14 "2026-04-09T09:12:37Z")

</div>

> [@nsajko](#):
>
> > [@jules](#):
> >
> > Julia has some heuristics which Union types it will choose in such scenarios and when it falls back to `Any`.
> 
> That is just `typejoin`.

Hm would you say so? These are not typejoin but are manually chosen exceptions (what I called heuristic but it might be the wrong word):

```julia
julia> [1, nothing]
2-element Vector{Union{Nothing, Int64}}:
 1
  nothing

julia> [1, missing]
2-element Vector{Union{Missing, Int64}}:
 1
  missing

julia> [1, missing, nothing]
3-element Vector{Union{Missing, Nothing, Int64}}:
 1
  missing
  nothing

julia> typejoin(Int64, Missing)
Any

julia> identity.(Any[1, missing, nothing])
3-element Vector{Union{Missing, Nothing, Int64}}:
 1
  missing
  nothing

```

---

<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:** [April 9, 2026, 2:42pm UTC](https://discourse.julialang.org/t/recommended-idiom-for-collecting-narrowest-type/136604/15 "2026-04-09T14:42:06Z")

</div>

Sorry, you’re right. It is not exactly `typejoin`. I suppose `promote_typejoin` is what `collect` uses, then.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [April 9, 2026, 3:12pm UTC](https://discourse.julialang.org/t/recommended-idiom-for-collecting-narrowest-type/136604/16 "2026-04-09T15:12:22Z")

</div>

> [@nsajko](#):
>
> I suppose `promote_typejoin` is what `collect` uses, then.

Right: [julia/base/array.jl at 1dbc40fca56998b09fd8b005a96bc0457d585d85 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/blob/1dbc40fca56998b09fd8b005a96bc0457d585d85/base/array.jl#L874)

(I was assuming that `promote_typejoin` was like `promote` but using `typejoin` instead of `promote_type`, but instead it is [a different type-promotion function](https://github.com/JuliaLang/julia/blob/1dbc40fca56998b09fd8b005a96bc0457d585d85/base/promotion.jl#L161-L182).)

---

<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:** [April 10, 2026, 1:59am UTC](https://discourse.julialang.org/t/recommended-idiom-for-collecting-narrowest-type/136604/17 "2026-04-10T01:59:47Z")

</div>

The public doc string says:

> Compute a type that contains both `T` and `S`, which could be either a parent of both types, or a `Union` if appropriate. Falls back to `typejoin`.

So `promote_typejoin` is a misnomer, it is just `typejoin`, with additional handling for some `Union`s. Nothing to do with promotion.
