453

Is there any simpler/nicer way of getting a slice of keys from a map in Go?

Currently I am iterating over the map and copying the keys to a slice:

i := 0
keys := make([]int, len(mymap))
for k := range mymap {
    keys[i] = k
    i++
}
1
  • 1
    slices.Collect(maps.Keys(someMap)), to get slice in go 1.23+ Commented Sep 17 at 15:12

10 Answers 10

583

This is an old question, but here's my two cents. PeterSO's answer is slightly more concise, but slightly less efficient. You already know how big it's going to be so you don't even need to use append:

keys := make([]int, len(mymap))

i := 0
for k := range mymap {
    keys[i] = k
    i++
}

In most situations it probably won't make much of a difference, but it's not much more work, and in my tests (using a map with 1,000,000 random int64 keys and then generating the array of keys ten times with each method), it was about 20% faster to assign members of the array directly than to use append.

Although setting the capacity eliminates reallocations, append still has to do extra work to check if you've reached capacity on each append.

Sign up to request clarification or add additional context in comments.

14 Comments

This looks exactly the same as the OP's code. I agree that this is the better way, but I'm curious if I've missed the difference between this answer's code and OP's code.
Good point, I somehow looked at the other answers and missed that my answer is exactly the same as the OP. Oh well, at least we now know approximately what the penalty is for using append unnecessarily :)
Why aren't you using an index with the range, for i, k := range mymap{. That way you don't need the i++?
Maybe I'm missing something here, but if you did i, k := range mymap, then i will be keys and k will be values corresponding to those keys in the map. That won't actually help you populate a slice of keys.
@Alaska if you're concerned with the cost of allocating one temporary counter variable, but think a function call is going to take less memory you should educate yourself on what actually happens when a function gets called. Hint: It's not a magical incantation that does things for free. If you think the currently accepted answer is safe under concurrent access, you also need to go back to the basics: blog.golang.org/go-maps-in-action#TOC_6.
|
315

For example,

package main

func main() {
    mymap := make(map[int]string)
    keys := make([]int, 0, len(mymap))
    for k := range mymap {
        keys = append(keys, k)
    }
}

To be efficient in Go, it's important to minimize memory allocations.

9 Comments

It's slightly better to set the actual size instead of capacity and avoid append altogether. See my answer for details.
Note that if mymap is not a local variable (and is therefore subject to growing/shrinking), this is the only proper solution - it ensures that if the size of mymap changes between the initialization of keys and the for loop, there won't be any out-of-bounds issues.
maps are not safe under concurrent access, neither solution is acceptable if another goroutine might change the map.
@darethas that is a common misconception. The race detector will flag this usage since 1.6. From the release notes: "As always, if one goroutine is writing to a map, no other goroutine should be reading or writing the map concurrently. If the runtime detects this condition, it prints a diagnosis and crashes the program." golang.org/doc/go1.6#runtime
@darethas why is this sometimes safe to do and sometimes a problem? From Vinay Pais comment it seems like this is a bad idea in general?
|
184

Go 1.23 (August 2024)

The function maps.Keys will be moved into the standard library, however as you can see from the method definition, it doesn't just return a slice of keys: it returns an iterator. Therefore:

Range over keys

The iterator is a function that you can directly use in a range clause:

import (
    "maps"
)

func main() {
    m := map[string]int{"alpha": 1, "bravo": 2}

    for k := range maps.Keys(m) {
        // ...
    }
}

Playground: https://go.dev/play/p/r_cm9KiTqqF?v=gotip

Collect keys into a slice

If you need the map keys into a slice, you have to collect them from the iterator using slices.Collect:

import (
    "fmt"
    "maps"
    "slices"
)

func main() {
    m := map[string]int{"alpha": 1, "bravo": 2}

    keys := slices.Collect(maps.Keys(m))
    fmt.Println(keys) // [alpha bravo]
}

Playground: https://go.dev/play/p/GSWz3xqnI-9?v=gotip

This is efficient because the iterator is a function that yields values on demand. Though if you find this cumbersome, you can still write your own generic function as the old golang.org/x/exp/maps.Keys did (sample code below).

Go 1.18+

You can get the keys of any map with golang.org/x/exp/maps.Keys.

Example usage:

    intMap := map[int]int{1: 1, 2: 2}
    intKeys := maps.Keys(intMap)
    // intKeys is []int
    fmt.Println(intKeys)

    strMap := map[string]int{"alpha": 1, "bravo": 2}
    strKeys := maps.Keys(strMap)
    // strKeys is []string
    fmt.Println(strKeys)

maps package is found in golang.org/x/exp/maps. This is experimental and outside of Go compatibility guarantee. They aim to move it into the std lib in Go 1.19 the future.

Playground: https://go.dev/play/p/fkm9PrJYTly

For those who don't like to import exp packages, here's the source code (originally authored by Ian Lance Taylor), which as you can see is very simple:

// Keys returns the keys of the map m.
// The keys will be an indeterminate order.
func Keys[M ~map[K]V, K comparable, V any](m M) []K {
    r := make([]K, 0, len(m))
    for k := range m {
        r = append(r, k)
    }
    return r
}

NOTE: In Go 1.21 a part of the maps package has been moved into the standard library, but not maps.Keys. For details see the Go issue maps: remove Keys and Values for Go 1.21 tl;dr the Keys method might end up having a different signature. So in Go 1.21 the solutions presented here (use x/exp/maps or copy the source) are still applicable.

5 Comments

What is the meaning of ~ in ~map[K]V?
what's the difference between for k := range maps.Keys(m) and for k := range m
@march1993 if you only need to iterate over map keys, there's no difference. It's a matter of taste, although I guess the usual range-over-map for k := range m is less surprising. maps.Keys is useful if you need to work with iterators, e.g. you can pass it to slices.Collect or other similar functions that accept iter.Seq
Very good answer, but warning that "Collect keys into a slice" does not guarantee a consistent return order across calls. e.g. you can get [alpha bravo] or [bravo alpha].
129

You also can take an array of keys with type []Value by method MapKeys of struct Value from package "reflect":

package main

import (
    "fmt"
    "reflect"
)

func main() {
    abc := map[string]int{
        "a": 1,
        "b": 2,
        "c": 3,
    }

    keys := reflect.ValueOf(abc).MapKeys()

    fmt.Println(keys) // [a b c]
}

9 Comments

I think this is a good approach if there is a chance of a concurrent map access: this won't panic if the map grows during the loop. About performance, I'm not really sure, but I suspect it outperforms the append solution.
@AtilaRomero Not sure that this solution has any advantages, but when use reflection for any purposes this is more useful, because it allows to take a key as Value typed directly.
Is there a way to convert this to []string?
@AtilaRomero I just tested it, it still panics when the list grows.
This is the answer i was looking for. I am trying merge two different "Entity" structs, both of which contain a map[string][]*Foo. So, for the first struct, i need to identify the keys, so i can look it up in the second struct by key, instead of ranging over every map value to see if its they key i want to merge into. Thanks!
|
24

I made a sketchy benchmark on the three methods described in other responses.

Obviously pre-allocating the slice before pulling the keys is faster than appending, but surprisingly, the reflect.ValueOf(m).MapKeys() method is significantly slower than the latter:

❯ go run scratch.go
populating
filling 100000000 slots
done in 56.630774791s
running prealloc
took: 9.989049786s
running append
took: 18.948676741s
running reflect
took: 25.50070649s

Here's the code: https://play.golang.org/p/Z8O6a2jyfTH (running it in the playground aborts claiming that it takes too long, so, well, run it locally.)

3 Comments

In your keysAppend function, you can set the capacity of the keys array with make([]uint64, 0, len(m)), which drastically changed the performance of that function for me.
@keithbhunter I agree, there is very little difference.
@keithbhunter Calling make([]int, len(m)) and make([]int, 0, len(m) are effectivelly the same thing: preallocates array in memory, which would completely defeats the purpose of the test.
17

A nicer way to do this would be to use append:

keys = []int{}
for k := range mymap {
    keys = append(keys, k)
}

Other than that, you’re out of luck—Go isn’t a very expressive language.

5 Comments

It is less efficient than the original though - append will do multiple allocations to grow the underlying array, and has to update the slice length every call. Saying keys = make([]int, 0, len(mymap)) will get rid of the allocations but I expect it will still be slower.
This answer is safer than using len(mymap), if someone else changes the map while the copy is beeing made.
@AtilaRomero is that true? I would assume that in such case, data may be corrupted completely, or is this specified somewhere in golang?
@Petr I think that should panic. There should not be two routines working on the same map. That's what sync.Map should be used for, or use a map with Mutexes
Concurrent access would cause a data race error, if testing with that enabled. Definitely something to avoid.
8

Assuming map is of type map[int]string, you could get keys and values using the experimental maps package from the standard library:

package main

import (
    "fmt"
    "golang.org/x/exp/maps"
)


func main() {
    mymap := map[int]string{1: "foo", 2: "bar", 3: "biz"}

    fmt.Println(maps.Keys(mymap))
    fmt.Println(maps.Values(mymap))
}

Output:

[2 3 1]
[bar biz foo]

1 Comment

It should be noted that the order of results from both maps.Keys and maps.Values are indeterminate, so the order of results from one cannot be assumed to match the other.
3

A generic version (go 1.18+) of Vinay Pai's answer.

// MapKeysToSlice extract keys of map as slice,
func MapKeysToSlice[K comparable, V any](m map[K]V) []K {
    keys := make([]K, len(m))

    i := 0
    for k := range m {
        keys[i] = k
        i++
    }
    return keys
}

Comments

2

Visit https://play.golang.org/p/dx6PTtuBXQW

package main

import (
    "fmt"
    "sort"
)

func main() {
    mapEg := map[string]string{"c":"a","a":"c","b":"b"}
    keys := make([]string, 0, len(mapEg))
    for k := range mapEg {
        keys = append(keys, k)
    }
    sort.Strings(keys)
    fmt.Println(keys)
}

Comments

1

There is a cool lib called lo

A Lodash-style Go library based on Go 1.18+ Generics (map, filter, contains, find...)

With this lib you could do many convinient operations like map, filter, reduce and more. Also there are some helpers for map type

Keys

Creates an array of the map keys.

keys := lo.Keys[string, int](map[string]int{"foo": 1, "bar": 2})
// []string{"bar", "foo"}

Values

Creates an array of the map values.

values := lo.Values[string, int](map[string]int{"foo": 1, "bar": 2})
// []int{1, 2}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.