Swift Module 3
Strings, text and collections interview questions
Interview questions on strings, text and collections: short warm-ups, then what each harder question tests, the first step of a good answer and its follow-ups.
Warm-up questions
Short questions with short answers, each answered in full.
-
Fresher
What does count return for a Swift String, and why is it not the number of bytes?
Answered in the lesson Strings and Unicode: characters that look right
-
Fresher
Why can't you subscript a Swift String with an Int?
Answered in the lesson String indices, Substring and fast text processing
-
Fresher
What is the difference between an array, a set and a dictionary in Swift?
-
Fresher
What is the difference between map, compactMap and flatMap in Swift?
All three call a closure on every element and return an array, and they differ in what they do with the closure's results. map keeps one result per element, so mapping with a closure that returns an optional gives an array of optionals, such as [Optional(4), nil, Optional(6)]. compactMap unwraps the results and drops the nils, so ["4", "W", "6"].compactMap { Int($0) } is [4, 6]. flatMap is for closures that return a sequence: it joins all the returned sequences into one array, so [[1, 4], [6]].flatMap { $0 } is [1, 4, 6]. Older Swift also had a flatMap that removed nils; it was renamed compactMap (SE-0187) because the two jobs are different. A quick test is the shape you need back: one result per element, only the values that exist, or every element of every result.
From the lesson map, filter, reduce and lazy sequences
-
Fresher
What is the difference between firstMatch(of:) and wholeMatch(of:) in Swift?
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Fresher to mid
A sign-up flow limits display names to 20 characters. The iOS app checks name.count, the web client a JavaScript string's length, and the server stores names in a column of 40 bytes. Hindi names and emoji pass one layer and fail another. Explain why, and design one rule every layer can apply.
- What it tests
- Knowing what count, UTF-16 length and UTF-8 bytes measure, choosing one definition for a limit, and shortening text without splitting a Character.
- First step
- Measure one name in all three units. नमस्ते is 3 Characters, 6 UTF-16 units and 18 UTF-8 bytes, and 👩🏽💻 is 1 Character, 7 units and 15 bytes, so the three checks disagree for exactly these users. Then decide which unit the product means by a character.
- Follow-ups
-
- Which limit would you choose, and why?
- How do you shorten a name that is too long?
- What would you test?
From the lesson Strings and Unicode: characters that look right
Model answer, Pro. Model answers open with any pass; prices are in the notice on this page.Opening the model answers with your pass…
Model answers open with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so the model answers can’t be opened at the moment. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Two accounts exist for the name José: one typed with é as a single scalar, one with e and a combining accent. Swift code that compares the names with == says they are equal, but the database's unique index accepted both. Why do the two disagree, and how do you prevent such duplicates?
- What it tests
- Understanding canonical equivalence, how Swift's == and hashing apply it, why a comparison of bytes differs, and normalising text at the system's boundary.
- First step
- Compare the bytes. José with U+00E9 is 5 bytes of UTF-8 and with U+0065 U+0301 it is 6, so a store that compares bytes sees two different names, while Swift's == compares Characters by canonical equivalence and sees one.
- Follow-ups
-
- Where would you normalise the text, and to which form?
- Would lower-casing the names fix it?
- What about look-alike letters from other scripts?
From the lesson Strings and Unicode: characters that look right
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Fresher to mid
A log importer reads 2 GB of logs one line at a time, each line about 4 KB, and keeps the first 12 characters of each line, a request ID, as keys of a [Substring: Int] dictionary. Memory keeps growing far beyond the size of the IDs. Explain why, and fix it.
- What it tests
- Knowing that a Substring shares and keeps its base string's storage, finding where a slice outlives its string, and converting at the point of storage.
- First step
- Look at what the keys hold. Each key is line.prefix(12), a Substring whose base is the whole line, so every stored ID keeps its 4 KB line alive. 100,000 IDs then keep about 400 MB of lines instead of about 1.2 MB of IDs.
- Follow-ups
-
- Why does Swift make slices Substrings at all?
- How would you show the problem in a measurement or a test?
- Where else can a Substring outlive its string?
From the lesson String indices, Substring and fast text processing
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
A function scans a 200,000-Character string by reading text[text.index(text.startIndex, offsetBy: i)] inside a loop over 0..<text.count. It takes minutes on long inputs. Why, and how would you rewrite it, including when each step must also look at the previous Character?
- What it tests
- Knowing why String has no integer subscript, the cost of index(_:offsetBy:), and rewriting with iteration, indices, an Array or the utf8 view.
- First step
- Count the steps. Reading position i walks i steps from the start, so the loop walks 0 + 1 + … + 199,999 steps, n(n − 1)/2, about 20 billion, where one pass would take 200,000.
- Follow-ups
-
- How do you keep the previous Character in one pass?
- When is Array(text) the right fix?
- When is the utf8 view the right fix?
From the lesson String indices, Substring and fast text processing
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
Before each batch an app saves history = readings, where readings holds 1,000,000 values, and then appends 100 new readings to readings. Profiling shows that the first append of every batch takes far longer than the other 99. Explain what happens, and give two ways to avoid the cost.
- What it tests
- Understanding copy-on-write, when a write to shared storage copies, and designing the flow of data so that needless copies do not happen.
- First step
- After history = readings, the two arrays share one storage. The first append to readings must not change history, so it copies all 1,000,000 values into new storage first; the other 99 appends then change that storage in place.
- Follow-ups
-
- How would you confirm that the copy happens?
- Does passing the array to a function cause the same copy?
- Would a class that wraps the array be better?
From the lesson Arrays, sets and dictionaries
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A report builds [String: Int] totals and prints them with for (name, total) in totals. A snapshot test that compares the output with a saved file fails now and then, although the totals are always right. Why, and how do you fix both the report and the test?
- What it tests
- Knowing that the order of a Set or Dictionary depends on seeded hashing, why it changes from run to run, and how to produce output in a stable order.
- First step
- Ask where the order comes from. A dictionary's order follows the hashes of its keys, and Swift's Hasher is usually seeded at random for each run, so the same totals can print in a different order on each run, and the snapshot compares the order too.
- Follow-ups
-
- Is it enough to rerun the test until it passes?
- What if the report must keep the order in which names first appeared?
- Can the random seeding be turned off?
From the lesson Arrays, sets and dictionaries
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A view model stores orders.lazy.filter(isSuspicious).map(enrich) in a property. The screen reads its count, then shows the list. Logs show enrich running twice for many orders, and isSuspicious, which calls a fraud service, uses up the service's quota early. What is happening, and how do you fix it?
- What it tests
- Knowing that a lazy sequence computes again on every read, spotting side effects in its closures, and deciding where to turn a pipeline into stored results.
- First step
- Count the reads. A lazy filter and map store closures, not results, so reading count runs isSuspicious on every order, and showing the list runs isSuspicious and enrich again. Every read of the property repeats the work and its side effects.
- Follow-ups
-
- When is lazy still the right choice here?
- How would a test catch the extra calls?
- What changes if the orders are a Set?
From the lesson map, filter, reduce and lazy sequences
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Fresher to mid
Rank players by runs, highest first, so that tied players share a rank, as in 1, 2, 2, 4, and players inside a tie are listed by name. Describe the Swift code, explain why the order must not depend on the input order, and give its cost.
- What it tests
- Sorting with a tie-breaker, knowing that Swift's sort is stable, computing competition ranks in one pass, and stating the complexity.
- First step
- Sort by runs descending and by name for equal runs, with one sorted(by:) call, O(n log n). Then walk the sorted list once: a player with the same runs as the one before takes that player's rank, and any other player's rank is its position plus one.
- Follow-ups
-
- Why not rely on the stable sort instead of a tie-breaker?
- How would dense ranking, 1, 2, 2, 3, differ?
- What if the list changes after every ball?
From the lesson map, filter, reduce and lazy sequences
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
A service you maintain validates user names with the pattern ^([a-z0-9]+)*$. A request with a 30-character name followed by an exclamation mark keeps one CPU busy for minutes. Explain what the regex engine is doing, how you would show it safely, and how you would fix the validator.
- What it tests
- Recognising nested quantifiers, explaining how backtracking grows, measuring the growth without risk, and fixing it with a flat or atomic pattern and a bound on the length.
- First step
- Look at the nesting. In ([a-z0-9]+)* the inner and the outer quantifier can share out the 30 characters in every possible way, the ! makes every way fail, and the engine tries them all: the number of ways doubles with each character.
- Follow-ups
-
- How would you measure the growth without a stopwatch?
- Which fix keeps the same meaning?
- What else belongs in front of the regex?
From the lesson Regex literals and RegexBuilder
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
You move the alert-parsing code of a macOS 14 app, which uses Regex, into a new Swift package. The package fails to build with "'Regex' is only available in macOS 13.0 or newer". Why, how do you fix it, and what changes if the same package must also serve an app that supports macOS 12?
- What it tests
- Understanding deployment targets and SwiftPM's platform minimums, availability checks, and choosing between raising a minimum and keeping a fallback.
- First step
- Compare the deployment targets. The app builds for macOS 14, where Regex exists; a package that names no platforms builds for SwiftPM's predefined minimum, which is older than macOS 13, so every use of Regex is refused.
- Follow-ups
-
- What does raising the minimum cost?
- How would the fallback for macOS 12 look?
- How do you test both paths?
From the lesson Regex literals and RegexBuilder
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes