Sooner or later every Kotlin project needs to store something by name. It might be a setting, a cached response, or a field from a JSON payload whose shape you don’t fully control. That’s what the Map interface is for. It lives in the kotlin.collections package and holds data as key-value pairs, with each key pointing to exactly one value.
JetBrains maintains it as part of Kotlin’s standard library. On the JVM it corresponds to java.util.Map, so a Kotlin function that takes a Map will accept a plain Java collection with no conversion step.
A lot of the performance behavior actually comes from the Java side. Java 8, released by Oracle in 2014, changed how the default implementation handles hash collisions. When one bucket reaches eight or more entries, it gets converted from a linked list into a red-black tree. Worst-case lookup then moves toward logarithmic time instead of a scan through every entry in that bucket.
What Is a Map in Kotlin?

You use it when you know what you want by name, not by position. Every key is unique, and only one value sits behind each key.
List and Set are Kotlin’s other core collection types. Map is the odd one out because it’s the only one built for lookup by key rather than by index.
- Put a second value under an existing key and the first one is simply replaced. No error, no warning.
- Values can repeat as much as you like. Ten different keys can all point to the same string.
You can also read the contents through entries, keys and values, which are read-only views.
Map has been in the standard library since Kotlin 1.0, and on the JVM it maps directly onto java.util.Map for interoperability with existing Java code.
Over 95% of the top 1,000 Android apps ship with Kotlin code, according to Google’s Android developer documentation (Google, 2023). A good chunk of that code uses Map to parse JSON responses, hold settings, or build lookup tables. Settings screens alone probably account for thousands of them.
If you want the bigger picture of the language first, the introduction to what Kotlin is covers the basics.
Spend any real time doing Android development and you will run into a Map before your first week is out.
How Does a Kotlin Map Differ From a List and a Set?
A List hands items back by position. Sets care about uniqueness and nothing else, and there’s no key to look anything up with. With a Map, you give it a key and you get a value.
You usually find out quickly when you picked the wrong one. A Set quietly drops a duplicate you needed, or you end up writing a loop over an entire List just to find one record.
| Type | Access Method | Duplicates | Typical Use |
|---|---|---|---|
| List | Index position | Allowed | Ordered sequence of items |
| Set | Membership check | Not allowed | Unique collection, no lookup key |
| Map | Key lookup | Values repeat, keys don’t | Config, cache, or lookup data |
Here’s the same small bit of data stored each way.
- As a List:
["Alice", "Bob", "Alice"]. Three entries, order kept, the duplicate is fine. - As a Set,
{"Alice", "Bob"}, because the second “Alice” gets dropped on insert - As a Map:
{"Alice" to 29, "Bob" to 34}, where each name now points to an age
I see this one a lot in code reviews. Someone stores records in a List, then writes a manual search loop to find one by ID. That loop is exactly the code a Map removes.
Map vs MutableMap: Which Kotlin Map Type Should You Use?

Map is read-only. MutableMap extends it and adds put, remove, and clear.
Kotlin enforces the split at compile time. It isn’t a convention you have to remember.
Returning a plain Map from a function means callers can’t change your data after you hand it over, which makes it the safer default for anything public. The object underneath can still be mutable. Callers just don’t get access to that.
MutableMap is what you want when the map changes after it’s created, like a cache or a counter. Returning one from a public function is a different story, though, since any caller can now edit your internal state directly. As a local variable that lives inside one function, it’s fine and often the simplest option.
Tightening a MutableMap to a Map later, after outside code has started depending on editing it, usually turns into a bigger code refactoring job than teams expect.
So start with Map. Widen to MutableMap only where the compiler actually complains.
How Do You Create a Map in Kotlin?

mapOf and mutableMapOf are what you’ll use nearly every time. The standard library also has hashMapOf, linkedMapOf, and sortedMapOf for cases where the backing class matters, and each function returns a slightly different mix of interface type and implementation.
| Function | Returns | Backing Class | Order Preserved |
|---|---|---|---|
| mapOf | Map | LinkedHashMap | Yes, insertion order |
| mutableMapOf | MutableMap | LinkedHashMap | Yes, insertion order |
| hashMapOf | HashMap | HashMap | No |
| linkedMapOf | LinkedHashMap | LinkedHashMap | Yes, insertion order |
| sortedMapOf | SortedMap | TreeMap | Yes, key sort order |
Passing the same entries to different functions shows the difference.
mapOf("a" to 1, "b" to 2, "c" to 3)is read-only and keeps ordermutableMapOf("a" to 1, "b" to 2, "c" to 3)keeps order too, but you can edit ithashMapOf("a" to 1, "b" to 2, "c" to 3), editable, order not guaranteedsortedMapOf("a" to 1, "b" to 2, "c" to 3)sorts by key
For an empty read-only map there’s emptyMap(). It’s been documented since Kotlin 1.0 and is defined to equal whatever mapOf() returns when you call it with zero arguments.
A Kotlin cheat sheet puts this shorthand next to the rest of Kotlin’s collection functions, which is handy when you forget which one returns what.
HashMap, LinkedHashMap and TreeMap: How Do Kotlin’s Default Map Implementations Differ?

According to Oracle’s Java documentation, LinkedHashMap runs just slightly slower than HashMap for get and put. The reason is an extra linked list it maintains behind the scenes.
What you get for that cost is predictable order. Iterate a LinkedHashMap and entries come back in the order you added them, every time.
| Class | Ordering | Backing Structure | Best For |
|---|---|---|---|
| HashMap | No guarantee | Hash table | Raw speed, order doesn’t matter |
| LinkedHashMap | Insertion order | Hash table plus linked list | Predictable iteration, Kotlin’s own default |
| TreeMap | Key sort order | Red-black tree | Sorted keys, range queries |
LinkedHashMap is already what mapOf and mutableMapOf give you, and I’d leave it that way unless you have a reason not to.
That reason is usually one of two things. Maybe order really doesn’t matter and the map is big enough that the linked list overhead shows up in a profiler, in which case HashMap makes sense. Or the keys need to stay sorted (a leaderboard keyed by score, a time series keyed by timestamp) and TreeMap is the obvious pick.
What Determines Lookup and Insertion Performance in a Kotlin Map?

Mostly, it depends on the backing class you picked. Each one has a documented cost, and none of it is Kotlin-specific. It all comes from the Java Collections Framework underneath.
Oracle’s Java documentation gives these numbers:
- HashMap get and put: constant-time performance, assuming the hash function spreads keys evenly across buckets (Oracle Java Documentation)
- HashMap default configuration: 16 buckets at a 0.75 load factor before the table resizes itself (Oracle Java Documentation)
- TreeMap containsKey, get, put, and remove: guaranteed log(n) time cost, backed by a red-black tree (Oracle Java Documentation)
In Big O notation that’s O(1) for HashMap lookup and O(log n) for TreeMap.
The HashMap number comes with a catch. It only holds with a well-behaved hash function. A poor one piles keys into the same buckets, and in the worst case lookup slides toward linear time.
LinkedHashMap also uses a bit more memory than a plain HashMap, because every entry carries extra linked list pointers. Oracle’s documentation doesn’t put a number on that. It describes the LinkedHashMap-versus-HashMap tradeoff in terms of time performance (“just slightly below that of HashMap”), not memory.
How Do You Get, Add, Remove and Check Values in a Kotlin Map?

Day to day, you’ll mostly use get, getOrDefault, getOrElse, put, remove, and containsKey.
Bracket syntax and get are interchangeable. map["key"] and map.get("key") do exactly the same thing.
Reading Values
map["key"]ormap.get("key")returns the value, or null if the key is missingmap.getValue("key")returns the value or throws NoSuchElementException if the key is missingmap.getOrDefault("key", fallback)gives you the fallback instead of nullmap.getOrElse("key") { computeFallback() }runs a lambda instead of using a hardcoded fallback
In Kotlin a missing key isn’t an error. It returns null by default, which fits the language’s general approach to null safety. getValue is the exception, since it throws.
Which one to use comes down to what a missing key means. For a normal optional lookup, plain get is fine and stays quiet. If a missing key means something has gone wrong in your code, use getValue so you find out right away and not three screens later.
Checking, Writing and Removing
map.containsKey("key") returns true if the key exists, whatever its value is. map.containsValue(value) checks by equality whether any key maps to that value.
map["key"] = valueormap.put("key", value)adds a new entry or overwrites an existing onemap.remove("key")removes the entry and returns the old value, or null if it wasn’t there
Call containsKey before a write only when the difference between a missing key and a key mapped to null matters to your logic. Most of the time it doesn’t, and the extra check is just noise.
How Do You Iterate, Destructure and Sort a Kotlin Map?

All of these work by going through the entries one at a time. Sorting and destructuring just use that access for different jobs.
Iterating With a For Loop and Destructuring
A for loop over a Map walks its entries by default and hands you a Map.Entry on each pass.
Destructuring pulls the key and value out of that entry. It works because Kotlin’s standard library provides component1() and component2() for Map.Entry.
The version I write most often is for ((key, value) in map), which destructures right in the loop header. If you’d rather keep the entry object, for (entry in map.entries) gives you entry.key and entry.value. There’s also map.forEach { (key, value) -> ... }, the same destructuring inside a lambda.
That forEach call is ordinary Kotlin lambda functions syntax, applied to one entry at a time.
If you only need one side of the pair, loop over map.keys or map.values and skip destructuring altogether.
Sorting Entries by Key or by Value
There’s no function that sorts a Map in place. Sorting a Map really means building a new one with a different key order.
For key order, map.toSortedMap() uses natural ordering, and entries.sortedBy { it.key } lets you plug in your own comparator. To sort by value, entries.sortedByDescending { it.value } puts the highest value first.
Every one of these returns a new List of entries or a new Map. The original, mutable or not, is never reordered.
How Do You Filter and Transform a Kotlin Map?

Filtering removes entries. Transforming keeps the entries and changes what’s in them, and Kotlin keeps those two jobs in separate function families.
For filtering you have filterKeys { predicate } and filterValues { predicate }. Each keeps the entries where the key (or the value) matches.
mapKeys { (k, v) -> newKey }leaves values alone and transforms the keysmapValues { (k, v) -> newValue }, same keys, new values
Building a map from some other collection goes the other way. associateBy and associateWith turn a List into a Map, while groupBy turns one into a Map of lists.
If two elements produce the same key, associateBy keeps only the last one, according to Kotlin’s own standard library documentation. That one has bitten me more than once.
A full list to map in Kotlin conversion, duplicate keys included, is worth reading about separately.
In practice these usually get chained together:
- Start with a List of raw objects, such as parsed rows or API results
- Call groupBy or associateBy to turn it into a Map keyed on the field you’ll search by
- Call filterKeys or filterValues to drop what the pipeline doesn’t need
- Call mapValues to reshape what’s left into the final output type
How Do You Convert a Kotlin Map and Use It With JSON, Android and Backend Frameworks?

You’ll convert Maps to Lists and back all the time, and JSON objects end up in a Map almost as often. Both use a fairly small set of functions.
Converting Between a Map and a List
map.toList() turns a Map into a List of Pair objects, one per entry, in the order the Map already iterates in. The return type is List<Pair<K, V>>.
Going back, listOfPairs.toMap() rebuilds the Map. A duplicate key keeps only the last pair, which is the same rule associateBy follows.
entries.toList() gives you a List of Map.Entry instead. That’s useful right before a sortedBy call.
Parsing JSON Into a Map
JSON objects don’t have a fixed key set the way a Kotlin class does. So parsers often deserialize them straight into a Map<String, Any> instead of a data class.
Gson requires a TypeToken, such as TypeToken<Map<String, Any>>(), to deserialize JSON into a Map, according to Gson’s own documentation.
| Library | Typical Call | Best For |
|---|---|---|
| Gson | fromJson(json, TypeToken<Map<String, Any>>().type) | Flexible JSON with no fixed schema |
| Jackson | readValue(json, Map::class.java) | Larger backend services, more config options |
| Retrofit | Response body typed as Map<String, Any> | API responses whose fields aren’t fixed in advance |
Retrofit response bodies come back in this shape pretty often, especially from a RESTful API that doesn’t return the same fields on every endpoint.
On the server side, Ktor and Spring Boot are both back-end development frameworks, and both read query parameters and path variables into a Map before your handler function ever sees them.
When Does a Kotlin Map Not Apply, or Fail to Perform Well?

Maps get used in places where they cause problems. These are the cases where I’d stop and pick something else.
Changing the Map While You Loop Over It
Modify a Map directly inside a for loop over its entries and you get ConcurrentModificationException.
Oracle’s Java documentation calls this fail-fast behavior, thrown on a best-effort basis rather than guaranteed every time. So you can’t count on it to catch the bug.
Use an explicit MutableIterator and its own remove() function, or loop over a copy instead of the live Map.
Several Coroutines Touching the Same Map
A standard Map or MutableMap is not thread-safe. Read and write one from several coroutines without coordination and the data gets corrupted silently. Nothing crashes, which is exactly what makes it hard to catch.
Kotlin’s own coroutines documentation shows this with a shared counter incremented by 100 concurrent coroutines with no synchronization. It almost never lands on the correct total.
The usual fix is ConcurrentHashMap. Its iterators don’t throw ConcurrentModificationException, and its default concurrency hint covers 16 simultaneous updates, per the same Java documentation.
Use it on purpose whenever a Map is shared by Kotlin coroutines that can reach it from more than one place at once.
A Key Set You Already Know
If every field is known at compile time, a data class is better than a Map in almost every way that matters.
The Kotlin compiler automatically generates equals(), hashCode(), toString(), and copy() for a data class from its constructor properties, a level of type safety a Map<String, Any> can’t offer, according to Kotlin’s own documentation on data classes.
A Map still makes sense when the keys are only known at runtime. Once they aren’t, switch to Kotlin data classes.
Access by Position
If what you actually need is the third item, not the item called X, use a List. A Map only adds overhead there.
FAQ on Maps In Kotlin
Can You Nest a Map Inside Another Map, or Store a Map of Lists?
Yes, there’s no limit on nesting. Map<String, Map<String, Int>> and Map<String, List<String>> are both valid, everyday types.
You read them left to right, one bracket per level. Each nested collection keeps its own read-only or mutable rules.
How Do You Merge Two Kotlin Maps Into One?
Use the plus operator. firstMap + secondMap returns a new Map, and the second map’s values win on any shared key.
MutableMap.putAll() does the same merge in place. Neither one checks for conflicts, so the last write always wins.
How Do You Convert a Mutable Map Back Into a Read-Only Map?
Call toMap(). It copies the entries into a new, read-only Map, which is more than just changing the compile-time type.
Assigning a MutableMap to a Map-typed variable hides mutation from callers, but the underlying object can still change. toMap() is the real conversion.
Does a Kotlin Map Preserve Insertion Order by Default?
In practice, yes. mapOf() and mutableMapOf() both return a LinkedHashMap underneath, and that iterates in insertion order.
hashMapOf() drops the guarantee. With sortedMapOf(), order follows the keys, not the sequence the entries were added in.
Where Does a Kotlin Map Break Down?
The real trouble spots are unsynchronized access from multiple coroutines or threads, structural changes during iteration, and a key set that turns out to be fixed rather than dynamic at runtime.
Fix concurrency first. It corrupts data without any error, so it’s the hardest one to catch in testing. Iteration bugs come next, and they’re the easy ones because the exception shows up right away. Key-set design can wait. It costs nothing today, but it gets more expensive as the codebase grows.
ConcurrentHashMap has one cost worth knowing about before you switch. It rejects null keys and null values outright, a restriction neither HashMap nor LinkedHashMap impose, according to Oracle’s Java documentation. If your code stores nulls, you’ll need to change that first.
If your Map already lives inside a coroutine, you’re not far from needing Kotlin flows, which apply the same concurrency thinking to a stream of values instead of a single snapshot.
- pip is not recognized in VS Code: Why It Happens and How to Fix It - October 1, 2026
- 10 Enterprise Server Virtualization Platforms for Modern Data Centers - October 1, 2026
- MySQL Cheat Sheet - September 30, 2026



