Built-in collections by language
Learning to hand-write data structures matters for interviews, but in real code you almost always reach for the language's built-in collection. What matters is knowing what each one is underneath and what its operations cost — because picking the wrong collection is the most common hidden performance problem there is.
Three questions are enough:
- What is it built on? A dynamic array, a hash table, a linked list, or a balanced tree?
- Is order preserved? Insertion order, sorted order, or no guarantee at all?
- Which operation dominates? Index access, lookup by key, insertion/removal at the ends, or membership tests?
The answers to those three make the choice almost automatic.
| Structure | JavaScript | Kotlin | Dart | What's inside |
|---|---|---|---|---|
| Dynamic array | `Array` | `List` / `MutableList` (`ArrayList`) | `List` | A contiguous buffer plus a length; O(1) index, amortized O(1) append |
| Hash map | `Map` (preserves insertion order) / a plain `object` | `HashMap`, `LinkedHashMap` (the default for `mapOf`) | `Map` (defaults to `LinkedHashMap`) | A hash table; get/put/delete O(1) average |
| Hash set | `Set` | `HashSet`, `LinkedHashSet` (the default for `setOf`) | `Set` (defaults to `LinkedHashSet`) | A hash table of keys only; `has` is O(1) |
| Deque / Queue | None — `Array` or hand-rolled | `ArrayDeque` | `Queue`, `ListQueue`, `DoubleLinkedQueue` | A circular buffer (or linked list); O(1) at both ends |
| Linked list | None | `java.util.LinkedList` (not recommended) | `LinkedList` (`dart:collection`) | Nodes plus pointers |
| Sorted map/set | None | `sortedMapOf`, `TreeMap` (JVM) | `SplayTreeMap`, `SplayTreeSet` | A balanced tree; O(log n) operations, order preserved |
| Weakly-referenced map | `WeakMap`, `WeakSet` | `WeakHashMap` (JVM) | `Expando`, `WeakReference` | The entry disappears when the key is collected — useful for caches |
Practical notes per language — these are what read as concrete knowledge in an interview.
JavaScript: Map beats a plain object — keys can be any type (objects included), insertion order is guaranteed, size is O(1), and there's no prototype-pollution risk. A plain object coerces every key to a string or symbol. Set turns membership checks from includes's O(n) into O(1). There is no built-in queue — since shift() is O(n), you keep a head index or write your own. Among array methods, push/pop are O(1) while shift/unshift/splice are O(n).
Kotlin: immutable and mutable interfaces are separate — listOf vs mutableListOf, mapOf vs mutableMapOf. That expresses intent at the type level. mapOf/setOf return LinkedHashMap/LinkedHashSet, which preserve insertion order; if order is irrelevant, HashMap is slightly faster. For stacks and queues the right choice is ArrayDeque — avoid the legacy java.util.Stack (synchronised, extends Vector) and LinkedList. Data classes generate equals/hashCode automatically, making them safe as map keys.
Dart: there is literal syntax for List, Map and Set ([], {}, {1, 2}) — note that an empty {} is a Map; for an empty set you write <int>{}. Queue lives in dart:collection, with ListQueue (a circular buffer) as the default implementation. const collections are built at compile time. Most collection methods return a lazy `Iterable` (map, where) — no work happens until you call toList(); that is both an advantage and a source of accidental recomputation.
Which to pick — the practical rule:
- Order matters and you need index access → List/Array
- Lookup by key → Map (
O(1)average) - Uniqueness or membership tests → Set
- Insertion/removal at both ends, a queue or a stack → Deque (
ArrayDeque,Queue) - Must stay sorted at all times → a sorted map/set (
O(log n)), or onesort()call - Removal by priority → a priority queue / heap (
O(log n))
In an interview a different rule applies. You are usually expected to write them by hand: linked list, stack, queue, a simple hash map, a binary search tree, a heap. The reason is that these structures are ideal for probing pointer manipulation, edge cases and complexity analysis.
The best move is to combine both: "In a real project I'd use `ArrayDeque`, since it's a circular buffer and gives `O(1)` at both ends. But if you'd like me to write it by hand, here's how..." That sentence demonstrates practical maturity and theoretical preparation at once.
Interview tip. A frequent question: "What's the difference between `Map` and a plain `object` (or between `HashMap` and `LinkedHashMap`)?" Cover three axes: key types, order guarantees, and performance and API. Give a concrete detail — e.g. in JS an object coerces keys to strings, so obj[1] and obj['1'] are the same slot, whereas in a Map they are different.
Second classic: "If a hash map is `O(1)` on average, what is it in the worst case?" — O(n), when all keys land in one bucket (a poor hash function, or deliberately crafted input — a hash-flooding attack). Modern implementations convert a hot bucket into a tree, bringing it to O(log n). Knowing that detail leaves a good impression.
Using a mutable object as a map key is the most common real-world bug: if the key's hash changes after insertion, the entry can never be found again. Keys must therefore be immutable (a Kotlin data class with val fields, immutable values in Dart).
Other typical mistakes: calling list.contains() inside a loop and creating O(n²) (use a Set); expecting order from a HashMap; using a class without equals/hashCode as a Kotlin map key; and trying to fake object keys in JS with JSON.stringify (a Map supports object keys natively).
📚 Sources and documentation
- Kotlin collections overviewofficialkotlinlang.org
- Dart collectionsofficialdart.dev