429

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++
}
0

10 Answers 10

566

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.

13
  • 81
    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.
    – Emmaly
    Mar 8, 2015 at 7:32
  • 8
    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 :)
    – Vinay Pai
    Mar 10, 2015 at 14:43
  • 7
    Why aren't you using an index with the range, for i, k := range mymap{. That way you don't need the i++?
    – mvndaai
    Apr 29, 2016 at 17:57
  • 51
    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.
    – Vinay Pai
    May 2, 2016 at 19:31
  • 5
    @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.
    – Vinay Pai
    Oct 8, 2018 at 23:31
309

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
  • 48
    It's slightly better to set the actual size instead of capacity and avoid append altogether. See my answer for details.
    – Vinay Pai
    Jan 8, 2015 at 19:37
  • 3
    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.
    – Melllvar
    Jul 25, 2017 at 21:23
  • 18
    maps are not safe under concurrent access, neither solution is acceptable if another goroutine might change the map.
    – Vinay Pai
    Jul 26, 2017 at 17:27
  • 6
    @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
    – Vinay Pai
    Aug 8, 2018 at 22:10
  • 1
    @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?
    – xuiqzy
    Sep 9, 2020 at 9:04
127

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]
}
7
  • 1
    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. Aug 8, 2018 at 16:28
  • @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. Aug 17, 2018 at 9:50
  • 46
    Is there a way to convert this to []string? May 29, 2019 at 15:13
  • 1
    @AtilaRomero I just tested it, it still panics when the list grows.
    – gtato
    Oct 7, 2020 at 19:13
  • 1
    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! Jun 29, 2021 at 13:22
116

Go now has generics. You can get the keys of any map with 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.

2
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
  • 5
    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. Apr 1, 2019 at 15:59
  • @keithbhunter I agree, there is very little difference. Mar 24, 2021 at 14:18
  • @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.
    – rdnobrega
    Apr 29, 2021 at 17:10
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
  • 18
    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. Jan 26, 2014 at 14:53
  • 1
    This answer is safer than using len(mymap), if someone else changes the map while the copy is beeing made. Aug 8, 2018 at 12:21
  • @AtilaRomero is that true? I would assume that in such case, data may be corrupted completely, or is this specified somewhere in golang?
    – Petr
    Sep 6, 2021 at 17:48
  • 1
    @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 Nov 9, 2021 at 4:17
  • Concurrent access would cause a data race error, if testing with that enabled. Definitely something to avoid. Jun 24, 2022 at 17:55
6

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
  • 1
    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.
    – Evan Byrne
    Jun 29, 2023 at 2:40
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
}
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)
}
0
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}

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.