Pocket Studio Academy
HomePart 11.11

Maps and Sets

Full course9 min read·4 questions

A list keeps things in order. A map looks things up by name, and a set refuses duplicates. After this you will know which of the three to reach for, and how to walk through a map's entries.

When a list is the wrong shape

You are keeping score for a game night. Four players, four scores. A can hold them:

Playground.ktkotlin
1val names = listOf("Ada", "Alan", "Grace", "Zoe")
2val points = listOf(12, 9, 15, 4)

Now answer one question: how many points does Grace have?

You have to find Grace's in the first list, then read the same index out of the second one. Two steps, two lists, and one gruesome bug waiting to happen — if you ever add a name without adding a score, every answer after Grace becomes wrong for ever.

The problem is not your code. The problem is that a list is the wrong shape for this job. You do not want positions. You want lookups.

Think of it like this

Think about the cloakroom at a theatre.

You hand over your coat and get a numbered ticket. Later you hand back ticket 47 and the attendant fetches coat 47. Nobody counts along the rail from the left-hand end. The ticket is the way in.

That is a : a ticket () that leads straight to one thing ().

Now think about the guest list on the door of the same theatre. Writing "Ada" on it twice does not let Ada in twice. The list only records whether a name is on it.

That is a : no positions, no duplicates, just membership.

A map is a lookup table

Playground.ktkotlin
1val scores = mapOf("Ada" to 12, "Grace" to 15)
2println(scores["Grace"])
3println(scores["Zoe"])

That prints:

text
115
2null

Three new things are happening here.

mapOf builds a map. Every entry is written as key to value. That little word to is real Kotlin — it builds a , which is just two values glued together.

Square brackets look things up. scores["Grace"] does not mean "position Grace". It means "the value filed under Grace".

A missing key gives you . Not a crash, not zero — null, which is Kotlin's word for there is nothing here. If you want a stand-in instead, the operator supplies one:

Playground.ktkotlin
println(scores["Zoe"] ?: 0)

That prints 0. Read ?: as "or else". Lesson 1.13 is entirely about null and this operator, so for now just take it as a handy fallback.

Keys are unique; values are not

Two players can both have fifteen points. No two entries can share a key. If you write the same key twice, the later one silently wins:

Playground.ktkotlin
1val m = mapOf("Ada" to 12, "Ada" to 15)
2println(m.size)
3println(m["Ada"])

That prints 1 then 15. There is only ever one Ada slot.

Walking through every entry

A for loop over a map hands you both halves at once:

Playground.ktkotlin
1for ((name, score) in scores) {
2  println("$name scored $score")
3}

The double brackets around (name, score) are — unpacking one entry into two names in a single move. Lesson 1.15 uses the same trick on your own types.

If you only want one half, scores.keys and scores.values give you each side on its own.

Changing a map

mapOf builds a read-only map. To add and remove entries you want a one:

Playground.ktkotlin
1val scores = mutableMapOf("Ada" to 12)
2scores["Zoe"] = 4        // add
3scores["Ada"] = 15       // replace
4scores.remove("Zoe")     // delete
5println(scores.containsKey("Ada"))
6println("Zoe" in scores)

That prints true then false. Note that in works on a map's keys, and reads almost like English.

Tip

val scores = mutableMapOf(...) is not a contradiction. The means this name will always point at this same map. The word mutable means the contents of that map can change. Locked label, open box.

A set holds each thing once

Playground.ktkotlin
1val tags = setOf("red", "blue", "red")
2println(tags.size)
3println("blue" in tags)

That prints 2 then true. The second "red" was simply absorbed.

A set has no positions, so tags[0] is not a thing you can write — the compiler rejects it. In exchange you get two superpowers:

  • Instant membership tests. "blue" in tags is fast even with a hundred thousand tags.
  • Free de-duplication. .toSet() on any throws the repeats away.
Playground.ktkotlin
1val rolls = listOf(6, 4, 6, 2, 4)
2println(rolls.toSet())

That prints [6, 4, 2] — each face once, in the order it was first seen.

mutableSetOf gives you add and remove. add hands back true if the item was new and false if it was already there, which is a tidy way to ask "have I seen this before?"

Which one when

What you needReach for
Things in order, duplicates allowed, read by positionList
Look a value up by a name or an idMap
A membership list where duplicates are meaninglessSet

You will use all three in Part 5. Pocket Notes stores notes in a list because order matters. Focus Flow looks up a session's settings by name in a map. And a "which days did I focus?" question is a set, because a day either counts or it does not.

Try it in Pocket Studio
  1. Open Pocket Studio and tap Projects.
  2. Open your Kotlin practice project — the one you have used since Lesson 1.1.
  3. Tap Editor and open Playground.kt.
  4. Select everything in the file and delete it.
  5. Type the ten-line program from the walkthrough above, exactly as written.
  6. Tap Run (the triangle) and watch the Output panel.

You should see exactly this:

text
1Ada: 15
2Alan: 9
3Players: 2
4Faces seen: [6, 4, 2]
  1. Now change line 4 from scores["Ada"] = 15 to scores["Zoe"] = 15 and run again.
  2. Players: becomes 3, and Ada is back on twelve points. You added a key instead of replacing one — proof that the brackets do both jobs depending on whether the key exists.
Error Doctor5 common errors
e: No set method providing array access
MeansYou tried to assign into a read-only map — one built with mapOf rather than mutableMapOf. A read-only map can be read with brackets but never written to.
FixChange mapOf(...) to mutableMapOf(...). The val in front can stay exactly as it is.
e: Type mismatch: inferred type is String but Int was expected
MeansA map remembers the type of its values. This one holds numbers, and you tried to store text in it — probably scores["Ada"] = "15" with quotes around the number.
FixDrop the quotes: scores["Ada"] = 15. Quotes make a ; no quotes makes an .
e: Type mismatch: inferred type is Int? but Int was expected
MeansLooking a key up might find nothing, so Kotlin hands you Int? — a number or null. You asked to store that in a plain Int, which is not allowed to be null.
FixSupply a fallback with ?:, as in val n: Int = scores["Zoe"] ?: 0. Lesson 1.13 covers why Kotlin is so strict about this.
e: Unresolved reference: add
MeansYou called add on something built by setOf or listOf. Those are read-only, so they have no add at all — the name does not exist to be called.
FixBuild it with mutableSetOf or mutableListOf instead.
e: No get method providing array access
MeansYou wrote something like tags[0] on a set. Sets have no positions, so there is nothing for a number in brackets to mean.
FixIf you genuinely need position, use a . If you only need to know whether something is in there, use "red" in tags.
Recap
  • A stores and pairs and looks things up by key, not by position. Keys are unique.
  • Looking up a missing key gives ; ?: supplies a fallback.
  • for ((k, v) in map) walks every entry, unpacking both halves at once.
  • A holds each item once. in tests membership, and .toSet() strips duplicates out of any collection.
  • mapOf and setOf are read-only; mutableMapOf and mutableSetOf can be changed.
  • Next: the collection functions that replace most of your loops — filter, map and sumOf turn ten lines into one.