Lesson 61 of 77 intermediate

DSA I: Arrays, Strings, HashMaps, Sets & Two Pointers

Pattern recognition for array and string problems — brute force to optimized, with Dart implementations

Open interactive version (quiz + challenge)

Real-world analogy

Think of an array like a row of numbered lockers. A HashMap is like a receptionist who knows exactly which locker holds your item without checking each one. Two pointers are like two people walking toward each other in a hallway — they cover the distance twice as fast.

What is it?

Array, string, HashMap, Set, and two-pointer patterns are the foundational DSA toolkit tested in mobile engineering interviews. Mastering pattern recognition — knowing which data structure eliminates a nested loop — is more valuable than memorizing solutions.

Real-world relevance

In Tixio, finding duplicate message IDs uses a Set for O(1) dedup. Building channel member lookup uses Map for O(1) access. Sliding window detects typing burst rate. These exact patterns appear in mobile engineer interviews — connecting theory to real work you've already done.

Key points

Code example

// Pattern 1: Two Sum — brute force → optimized
// Brute force: O(n²) time, O(1) space
List<int> twoSumBrute(List<int> nums, int target) {
  for (int i = 0; i < nums.length; i++) {
    for (int j = i + 1; j < nums.length; j++) {
      if (nums[i] + nums[j] == target) return [i, j];
    }
  }
  return [];
}

// Optimized: O(n) time, O(n) space
List<int> twoSum(List<int> nums, int target) {
  final seen = <int, int>{}; // value → index
  for (int i = 0; i < nums.length; i++) {
    final complement = target - nums[i];
    if (seen.containsKey(complement)) return [seen[complement]!, i];
    seen[nums[i]] = i;
  }
  return [];
}

// Pattern 2: Longest substring without repeating chars
// Sliding window: O(n) time, O(min(n,charset)) space
int lengthOfLongestSubstring(String s) {
  final seen = <String, int>{}; // char → last index
  int maxLen = 0, left = 0;
  for (int right = 0; right < s.length; right++) {
    final c = s[right];
    if (seen.containsKey(c) && seen[c]! >= left) {
      left = seen[c]! + 1; // shrink window
    }
    seen[c] = right;
    maxLen = maxLen > right - left + 1 ? maxLen : right - left + 1;
  }
  return maxLen;
}

// Pattern 3: Group by category (mobile-relevant)
Map<String, List<String>> groupByWorkspace(List<Map<String,String>> messages) {
  final grouped = <String, List<String>>{};
  for (final msg in messages) {
    grouped.putIfAbsent(msg['workspace']!, () => []).add(msg['text']!);
  }
  return grouped;
}

Line-by-line walkthrough

  1. 1. twoSum takes a list of numbers and a target sum
  2. 2. seen Map stores each number mapped to its index as we iterate
  3. 3. For each number, calculate its complement: target - nums[i]
  4. 4. If complement is already in seen, we found our pair — return both indices
  5. 5. Otherwise store current number's index in seen for future lookups
  6. 6. This single pass replaces the nested loop — O(n) instead of O(n²)
  7. 7. lengthOfLongestSubstring uses a sliding window with two pointers
  8. 8. left marks the start of the current valid window
  9. 9. When a duplicate char is found within the window, advance left past it
  10. 10. seen Map tracks last index of each character for O(1) window shrinking
  11. 11. maxLen is updated on every iteration — no need for a second pass
  12. 12. groupByWorkspace uses putIfAbsent to lazily initialize lists — clean Dart pattern

Spot the bug

// Find all duplicates in a list
List<int> findDuplicates(List<int> nums) {
  final result = <int>[];
  for (int i = 0; i < nums.length; i++) {
    for (int j = 0; j < nums.length; j++) {
      if (i != j && nums[i] == nums[j] && !result.contains(nums[i])) {
        result.add(nums[i]);
      }
    }
  }
  return result;
}
Need a hint?
This is O(n³) — three nested linear operations. How can you reduce this to O(n)?
Show answer
O(n³) because: outer loop O(n) × inner loop O(n) × result.contains() O(n). Fix: use a Set<int> seen and Set<int> duplicates. Single pass: if seen.contains(x) add to duplicates, else add to seen. O(n) time, O(n) space. Code: final seen=<int>{}; final dups=<int>{}; for(var x in nums){if(!seen.add(x))dups.add(x);} return dups.toList();

Explain like I'm 5

Imagine you have a row of boxes with numbers. If someone asks 'do any two numbers add up to 10?', you could check every pair (slow). Or you could write down each number you've seen on a sticky note, and for each new number ask 'is 10-minus-this-number on a sticky note?' — that's a HashMap making it super fast.

Fun fact

The HashMap was invented in 1953 by Hans Peter Luhn at IBM. Today, it's the single most commonly tested data structure in mobile engineering interviews — because it converts nearly every O(n²) problem into O(n).

Hands-on challenge

Solve 'Find all pairs in a list that sum to a target value' — first write the O(n²) brute force, then optimize to O(n) using a Set. Then extend to return the indices using a HashMap. Test with: [2,7,11,15], target=9.

More resources

Open interactive version (quiz + challenge) ← Back to course: Flutter Interview Mastery