Question #446MediumDSADart BasicsImportant

How to remove the duplicate items in the list without using any built in methods like (contains, toSet(), where, indexOf) in flutter with some proper examples

#dart#dsa#list#duplicates#algorithm#interview#loop

Answer

Overview

This is a classic DSA interview question that tests your ability to solve problems from scratch without relying on Dart's built-in convenience methods. You must manually track which items you've already seen and build a new list with only unique items.


Method 1: Nested For Loop (Brute Force)

Compare each element with every element in the result list manually.

dart
List<int> removeDuplicates(List<int> input) {
  List<int> result = [];

  for (int i = 0; i < input.length; i++) {
    bool isDuplicate = false;

    // Check if input[i] already exists in result
    for (int j = 0; j < result.length; j++) {
      if (input[i] == result[j]) {
        isDuplicate = true;
        break;  // No need to check further
      }
    }

    // Only add if not found in result
    if (!isDuplicate) {
      result.add(input[i]);
    }
  }

  return result;
}

void main() {
  List<int> numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
  print(removeDuplicates(numbers));  // [3, 1, 4, 5, 9, 2, 6]
}

Time Complexity: O(n²) — for each element, scan the result list Space Complexity: O(n) — result list


Method 2: Using a Map to Track Seen Items

Use a

text
Map
as a manual hash set to check if we've seen an item before. This is much faster than nested loops.

dart
List<int> removeDuplicatesWithMap(List<int> input) {
  List<int> result = [];
  Map<int, bool> seen = {};  // Track which items we've seen

  for (int i = 0; i < input.length; i++) {
    if (seen[input[i]] == null) {  // Not seen before (no contains!)
      seen[input[i]] = true;       // Mark as seen
      result.add(input[i]);        // Add to result
    }
  }

  return result;
}

void main() {
  List<int> numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
  print(removeDuplicatesWithMap(numbers));  // [3, 1, 4, 5, 9, 2, 6]
}

Time Complexity: O(n) — single pass, Map lookup is O(1) Space Complexity: O(n) — Map + result list

Why this is better: Map key lookup (

text
map[key]
) is O(1) average, so the total is O(n) instead of O(n²).


Method 3: Sort First, Then Remove Adjacent Duplicates

Sort the list first so duplicates are next to each other, then just compare each element with the previous one.

dart
// Simple bubble sort (no built-in sort)
List<int> bubbleSort(List<int> input) {
  List<int> list = [];
  for (int i = 0; i < input.length; i++) {
    list.add(input[i]);  // Manual copy
  }

  for (int i = 0; i < list.length - 1; i++) {
    for (int j = 0; j < list.length - i - 1; j++) {
      if (list[j] > list[j + 1]) {
        // Swap
        int temp = list[j];
        list[j] = list[j + 1];
        list[j + 1] = temp;
      }
    }
  }
  return list;
}

List<int> removeDuplicatesSorted(List<int> input) {
  if (input.length == 0) return [];

  List<int> sorted = bubbleSort(input);
  List<int> result = [sorted[0]];  // First element is always unique

  for (int i = 1; i < sorted.length; i++) {
    // Only add if different from previous element
    if (sorted[i] != sorted[i - 1]) {
      result.add(sorted[i]);
    }
  }

  return result;
}

void main() {
  List<int> numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
  print(removeDuplicatesSorted(numbers));  // [1, 2, 3, 4, 5, 6, 9]
  // ⚠️ Note: order changes because of sorting
}

Time Complexity: O(n²) for bubble sort + O(n) for dedup = O(n²) Space Complexity: O(n)

Trade-off: This changes the original order. Use Method 1 or 2 if order must be preserved.


Method 4: In-Place Removal (No Extra List)

Remove duplicates by modifying the original list — no extra result list needed.

dart
List<int> removeDuplicatesInPlace(List<int> input) {
  // Create a working copy
  List<int> list = [];
  for (int i = 0; i < input.length; i++) {
    list.add(input[i]);
  }

  int i = 0;
  while (i < list.length) {
    int j = i + 1;
    while (j < list.length) {
      if (list[i] == list[j]) {
        // Remove duplicate by shifting elements left
        for (int k = j; k < list.length - 1; k++) {
          list[k] = list[k + 1];
        }
        list.removeLast();  // Shrink list
        // Don't increment j — new element shifted into position j
      } else {
        j++;
      }
    }
    i++;
  }

  return list;
}

void main() {
  List<int> numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
  print(removeDuplicatesInPlace(numbers));  // [3, 1, 4, 5, 9, 2, 6]
}

Time Complexity: O(n²) — nested loops with shifting Space Complexity: O(1) extra (modifies in-place)


Method 5: Works with Strings and Custom Objects

String List

dart
List<String> removeDuplicateStrings(List<String> input) {
  List<String> result = [];
  Map<String, bool> seen = {};

  for (int i = 0; i < input.length; i++) {
    if (seen[input[i]] == null) {
      seen[input[i]] = true;
      result.add(input[i]);
    }
  }

  return result;
}

void main() {
  List<String> fruits = ['apple', 'banana', 'apple', 'cherry', 'banana', 'date'];
  print(removeDuplicateStrings(fruits));  // [apple, banana, cherry, date]
}

Custom Objects (Compare by specific field)

dart
class User {
  String id;
  String name;
  User(this.id, this.name);

  
  String toString() => 'User($id, $name)';
}

List<User> removeDuplicateUsers(List<User> input) {
  List<User> result = [];
  Map<String, bool> seenIds = {};

  for (int i = 0; i < input.length; i++) {
    if (seenIds[input[i].id] == null) {
      seenIds[input[i].id] = true;
      result.add(input[i]);
    }
  }

  return result;
}

void main() {
  List<User> users = [
    User('1', 'Alice'),
    User('2', 'Bob'),
    User('1', 'Alice Duplicate'),  // Same ID
    User('3', 'Charlie'),
    User('2', 'Bob Duplicate'),    // Same ID
  ];

  print(removeDuplicateUsers(users));
  // [User(1, Alice), User(2, Bob), User(3, Charlie)]
}

Generic Version (Works with Any Type)

dart
List<T> removeDups<T>(List<T> input, String Function(T) getKey) {
  List<T> result = [];
  Map<String, bool> seen = {};

  for (int i = 0; i < input.length; i++) {
    String key = getKey(input[i]);
    if (seen[key] == null) {
      seen[key] = true;
      result.add(input[i]);
    }
  }

  return result;
}

void main() {
  // Works with int
  var nums = removeDups<int>([1, 2, 1, 3, 2], (n) => n.toString());
  print(nums);  // [1, 2, 3]

  // Works with String
  var strs = removeDups<String>(['a', 'b', 'a'], (s) => s);
  print(strs);  // [a, b]

  // Works with User
  var users = removeDups<User>(
    [User('1', 'Alice'), User('1', 'Dup')],
    (u) => u.id,
  );
  print(users);  // [User(1, Alice)]
}

All Methods Comparison

MethodTimeSpacePreserves Order?Best For
Nested LoopO(n²)O(n)YesSmall lists, simple approach
Map TrackingO(n)O(n)YesBest overall performance
Sort + AdjacentO(n²)O(n)NoWhen order doesn't matter
In-PlaceO(n²)O(1)YesMemory-constrained
GenericO(n)O(n)YesCustom objects, reusable

Interview Tips

If asked this question:

  1. Start with Method 1 (Nested Loop) — shows you understand the problem
  2. Then optimize to Method 2 (Map) — shows you can think about time complexity
  3. Mention Method 3 (Sort) as an alternative — shows breadth of knowledge
  4. Discuss trade-offs — order preservation vs performance

Key Takeaway: Without built-in methods, use a

text
Map
as a manual hash set for O(n) performance. The Map key lookup (
text
map[key] == null
) replaces
text
contains()
without using it directly.