Skip to content
  • Set · DSA Basics

Set

LearnViaNotes @learnvianotes· 21 min read

A set stores distinct elements with no order and no index, and answers is x present? in O(1) on average. It is a hash table that stores keys only — the right structure for de-duplication, membership tests and set algebra.

A hash set is a hash table whose slots hold just the keys
add(10), add(20), add(30), add(20)  with  h(x) = x % 4

slot 0 │ 20 │       ← the second add(20) finds 20 already here and does nothing
slot 1 │    │
slot 2 │ 10 │──▶│ 30 │
slot 3 │    │

size = 3, iteration order follows the slots, not the insertion order

Properties of a set

OperationPythonAverageWhy
adds.add(x)O(1)Hash x, place it in its slot if absent
removes.remove(x) (error if missing) / s.discard(x) (silent)O(1)Hash x, unlink it from its slot
membershipx in sO(1)Hash x, compare with the keys in that slot only
sizelen(s)O(1)A counter maintained on add/remove
union / intersection / differencea | b, a & b, a - b, a ^ bO(len(a) + len(b))Iterate one set, test membership in the other
subset / superset / disjointa <= b, a >= b, a.isdisjoint(b)O(min(len(a), len(b)))Test every element of the smaller set against the other
iteratefor x in sO(n)Walk every slot

Set containers in the four languages

LanguageHash set (unordered, O(1) avg)Ordered set (sorted, O(log n))
Pythonset, immutable frozenset— (use sorted(s) when needed, or bisect on a list)
C++std::unordered_setstd::set (red-black tree)
JavaHashSet; LinkedHashSet keeps insertion orderTreeSet (red-black tree)
JavaScriptSet (keeps insertion order by specification)— (sort an array)

Creating a set and adding elements

A set is built from a literal or from any iterable (duplicates are dropped on the way in). Elements are added one at a time or in bulk. Because iteration order differs between languages, the programs below sort before printing.

Python
def show(label, s):
    print(label + ":", *sorted(s))

s2 = set([1, 2, 3, 4, 1, 2, 3, 4])        # duplicates dropped
show("from list with duplicates", s2)
print("size:", len(s2))

s = {10, 20}                                # a set literal
s.add(30)                                   # one element
show("after add 30", s)
s.update([40, 50])                          # any iterable
s.update([60, 70], {80, 90})                # several at once
show("after bulk add", s)
print("size:", len(s))
C++
#include <iostream>
#include <unordered_set>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;

void show(const string& label, const unordered_set<int>& s) {
    vector<int> v(s.begin(), s.end());
    sort(v.begin(), v.end());
    cout << label << ":";
    for (int x : v) cout << " " << x;
    cout << "\n";
}

int main() {
    vector<int> data = {1, 2, 3, 4, 1, 2, 3, 4};
    unordered_set<int> s2(data.begin(), data.end());   // duplicates dropped
    show("from list with duplicates", s2);
    cout << "size: " << s2.size() << "\n";

    unordered_set<int> s = {10, 20};
    s.insert(30);                                       // one element
    show("after add 30", s);
    s.insert({40, 50});                                 // several at once
    for (int x : {60, 70, 80, 90}) s.insert(x);
    show("after bulk add", s);
    cout << "size: " << s.size() << "\n";
}
Java
import java.util.*;

public class Main {
    static void show(String label, Set<Integer> s) {
        StringBuilder sb = new StringBuilder(label + ":");
        for (int x : new TreeSet<>(s)) sb.append(" ").append(x);
        System.out.println(sb);
    }

    public static void main(String[] args) {
        Set<Integer> s2 = new HashSet<>(List.of(1, 2, 3, 4, 1, 2, 3, 4));   // duplicates dropped
        show("from list with duplicates", s2);
        System.out.println("size: " + s2.size());

        Set<Integer> s = new HashSet<>(List.of(10, 20));
        s.add(30);                                   // one element
        show("after add 30", s);
        s.addAll(List.of(40, 50));                   // any collection
        s.addAll(List.of(60, 70, 80, 90));
        show("after bulk add", s);
        System.out.println("size: " + s.size());
    }
}
JavaScript
const show = (label, s) => console.log(label + ":", [...s].sort((a, b) => a - b).join(" "));

const s2 = new Set([1, 2, 3, 4, 1, 2, 3, 4]);   // duplicates dropped
show("from list with duplicates", s2);
console.log("size:", s2.size);

const s = new Set([10, 20]);
s.add(30);                                      // one element
show("after add 30", s);
for (const x of [40, 50]) s.add(x);             // no bulk add — loop
for (const x of [60, 70, 80, 90]) s.add(x);
show("after bulk add", s);
console.log("size:", s.size);
Output
from list with duplicates: 1 2 3 4
size: 4
after add 30: 10 20 30
after bulk add: 10 20 30 40 50 60 70 80 90
size: 9

Time O(1) average per add · Space O(n)

Removing elements

Python offers two ways to remove: discard(x) silently ignores a missing element, remove(x) raises KeyError. C++ erase, Java remove and JavaScript delete behave like discard and report whether anything was removed. clear() empties the set in every language.

Python
def show(label, s):
    print(label + ":", *sorted(s))

s = {10, 20, 30}
s.discard(30)                               # silent if missing
show("after discard 30", s)
removed = 20 in s
s.remove(20)                                # KeyError if missing
print("removed 20:", "true" if removed else "false")
show("after remove 20", s)
removed = 99 in s
s.discard(99)
print("removed 99:", "true" if removed else "false")
s.clear()
print("after clear, size:", len(s))
C++
#include <iostream>
#include <unordered_set>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;

void show(const string& label, const unordered_set<int>& s) {
    vector<int> v(s.begin(), s.end());
    sort(v.begin(), v.end());
    cout << label << ":";
    for (int x : v) cout << " " << x;
    cout << "\n";
}

int main() {
    unordered_set<int> s = {10, 20, 30};
    s.erase(30);                                        // returns number erased (0 or 1)
    show("after discard 30", s);
    cout << "removed 20: " << (s.erase(20) ? "true" : "false") << "\n";
    show("after remove 20", s);
    cout << "removed 99: " << (s.erase(99) ? "true" : "false") << "\n";
    s.clear();
    cout << "after clear, size: " << s.size() << "\n";
}
Java
import java.util.*;

public class Main {
    static void show(String label, Set<Integer> s) {
        StringBuilder sb = new StringBuilder(label + ":");
        for (int x : new TreeSet<>(s)) sb.append(" ").append(x);
        System.out.println(sb);
    }

    public static void main(String[] args) {
        Set<Integer> s = new HashSet<>(List.of(10, 20, 30));
        s.remove(30);                                // returns boolean, no exception
        show("after discard 30", s);
        System.out.println("removed 20: " + s.remove(20));
        show("after remove 20", s);
        System.out.println("removed 99: " + s.remove(99));
        s.clear();
        System.out.println("after clear, size: " + s.size());
    }
}
JavaScript
const show = (label, s) => console.log(label + ":", [...s].sort((a, b) => a - b).join(" "));

const s = new Set([10, 20, 30]);
s.delete(30);                                   // returns boolean, no exception
show("after discard 30", s);
console.log("removed 20:", s.delete(20));
show("after remove 20", s);
console.log("removed 99:", s.delete(99));
s.clear();
console.log("after clear, size:", s.size);
Output
after discard 30: 10 20
removed 20: true
after remove 20: 10
removed 99: false
after clear, size: 0

Time O(1) average per removal · Space O(1)

Membership and aggregates

x in s is the operation sets exist for: hash x, look in one slot. The aggregates len, max, min, sum are ordinary iterations — O(1) for len (a stored counter) and O(n) for the rest, because a hash set keeps no order.

Python
s = {10, 20, 30, 40}
print("size:", len(s))                     # O(1)
print("max:", max(s))                      # O(n) — a hash set is not sorted
print("min:", min(s))
print("sum:", sum(s))
print("20 in s:", "true" if 20 in s else "false")   # O(1) average
print("50 in s:", "true" if 50 in s else "false")
C++
#include <iostream>
#include <unordered_set>
#include <algorithm>
#include <numeric>
using namespace std;

int main() {
    unordered_set<int> s = {10, 20, 30, 40};
    cout << "size: " << s.size() << "\n";                       // O(1)
    cout << "max: " << *max_element(s.begin(), s.end()) << "\n";   // O(n) — not sorted
    cout << "min: " << *min_element(s.begin(), s.end()) << "\n";
    cout << "sum: " << accumulate(s.begin(), s.end(), 0) << "\n";
    cout << "20 in s: " << (s.count(20) ? "true" : "false") << "\n";   // O(1) average
    cout << "50 in s: " << (s.contains(50) ? "true" : "false") << "\n"; // C++20
}
Java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Set<Integer> s = new HashSet<>(List.of(10, 20, 30, 40));
        System.out.println("size: " + s.size());                      // O(1)
        System.out.println("max: " + Collections.max(s));             // O(n) — not sorted
        System.out.println("min: " + Collections.min(s));
        int sum = 0;
        for (int x : s) sum += x;
        System.out.println("sum: " + sum);
        System.out.println("20 in s: " + s.contains(20));             // O(1) average
        System.out.println("50 in s: " + s.contains(50));
    }
}
JavaScript
const s = new Set([10, 20, 30, 40]);
console.log("size:", s.size);                       // O(1)
console.log("max:", Math.max(...s));                // O(n) — not sorted
console.log("min:", Math.min(...s));
console.log("sum:", [...s].reduce((a, b) => a + b, 0));
console.log("20 in s:", s.has(20));                 // O(1) average
console.log("50 in s:", s.has(50));
Output
size: 4
max: 40
min: 10
sum: 100
20 in s: true
50 in s: false

Time O(1) membership, O(n) aggregates · Space O(1)

Set algebra: union, intersection, difference

The four set operations on two sets that share one element
s1 = {10, 20, 30}      s2 = {30, 40, 50}

union          s1 | s2   = {10, 20, 30, 40, 50}   everything in either
intersection   s1 & s2   = {30}                   only what is in both
difference     s1 - s2   = {10, 20}               in s1 but not in s2
symmetric diff s1 ^ s2   = {10, 20, 40, 50}       in exactly one of them

Each operation iterates one set and tests membership in the other, so it costs O(|s1| + |s2|). Python has operators and named methods (s1.union(s2)); C++ needs a loop (or std::set_union on sorted containers); Java uses the mutating addAll / retainAll / removeAll on a copy; JavaScript gained union, intersection, difference and symmetricDifference methods on Set in ES2025 (Node 22+).

Python
def show(label, s):
    print(label + ":", *sorted(s))

s1 = {10, 20, 30}
s2 = {30, 40, 50}
show("union", s1 | s2)                      # also s1.union(s2)
show("intersection", s1 & s2)               # s1.intersection(s2)
show("difference", s1 - s2)                 # s1.difference(s2)
show("symmetric difference", s1 ^ s2)       # s1.symmetric_difference(s2)
C++
#include <iostream>
#include <unordered_set>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
using Set = unordered_set<int>;

void show(const string& label, const Set& s) {
    vector<int> v(s.begin(), s.end());
    sort(v.begin(), v.end());
    cout << label << ":";
    for (int x : v) cout << " " << x;
    cout << "\n";
}

Set unionOf(const Set& a, const Set& b) { Set r = a; r.insert(b.begin(), b.end()); return r; }
Set intersection(const Set& a, const Set& b) {
    Set r;
    for (int x : a) if (b.count(x)) r.insert(x);     // in both
    return r;
}
Set difference(const Set& a, const Set& b) {
    Set r;
    for (int x : a) if (!b.count(x)) r.insert(x);    // in a, not in b
    return r;
}
Set symmetricDifference(const Set& a, const Set& b) {
    return unionOf(difference(a, b), difference(b, a));
}

int main() {
    Set s1 = {10, 20, 30}, s2 = {30, 40, 50};
    show("union", unionOf(s1, s2));
    show("intersection", intersection(s1, s2));
    show("difference", difference(s1, s2));
    show("symmetric difference", symmetricDifference(s1, s2));
}
Java
import java.util.*;

public class Main {
    static void show(String label, Set<Integer> s) {
        StringBuilder sb = new StringBuilder(label + ":");
        for (int x : new TreeSet<>(s)) sb.append(" ").append(x);
        System.out.println(sb);
    }

    public static void main(String[] args) {
        Set<Integer> s1 = Set.of(10, 20, 30), s2 = Set.of(30, 40, 50);

        Set<Integer> union = new HashSet<>(s1); union.addAll(s2);
        Set<Integer> inter = new HashSet<>(s1); inter.retainAll(s2);      // keep only what is in s2
        Set<Integer> diff = new HashSet<>(s1); diff.removeAll(s2);        // drop what is in s2
        Set<Integer> sym = new HashSet<>(union); sym.removeAll(inter);    // union minus intersection

        show("union", union);
        show("intersection", inter);
        show("difference", diff);
        show("symmetric difference", sym);
    }
}
JavaScript
const show = (label, s) => console.log(label + ":", [...s].sort((a, b) => a - b).join(" "));

const s1 = new Set([10, 20, 30]);
const s2 = new Set([30, 40, 50]);
show("union", s1.union(s2));                            // ES2025 Set methods
show("intersection", s1.intersection(s2));
show("difference", s1.difference(s2));
show("symmetric difference", s1.symmetricDifference(s2));
Output
union: 10 20 30 40 50
intersection: 30
difference: 10 20
symmetric difference: 10 20 40 50

Time O(|s1| + |s2|) · Space O(|s1| + |s2|)

Subset, superset and disjoint tests

TestMeaningPythonCost
a.isdisjoint(b)No common elementa.isdisjoint(b)O(min(|a|, |b|))
a <= bEvery element of a is in b (subset, possibly equal)a.issubset(b)O(|a|)
a < bSubset and a != b (proper subset)a < bO(|a|)
a >= ba contains every element of b (superset)a.issuperset(b)O(|b|)
a > bProper superseta > bO(|b|)
Python
def b(x): return "true" if x else "false"

s1 = {10, 20}
s2 = {40, 30, 50}
print("s1 disjoint s2:", b(s1.isdisjoint(s2)))   # no common element
print("s2 disjoint s1:", b(s2.isdisjoint(s1)))
print("s1 disjoint s1:", b(s1.isdisjoint(s1)))   # a non-empty set shares everything with itself
print("s1 <= s2:", b(s1 <= s2))                   # subset
print("s1 < s2:", b(s1 < s2))                     # proper subset
print("s1 >= s2:", b(s1 >= s2))                   # superset
print("s1 > s2:", b(s1 > s2))                     # proper superset
print("{10} <= s1:", b({10} <= s1))
print("{10} < s1:", b({10} < s1))
print("s1 <= s1:", b(s1 <= s1))                   # a set is a subset of itself …
print("s1 < s1:", b(s1 < s1))                     # … but not a proper one
C++
#include <iostream>
#include <unordered_set>
using namespace std;
using Set = unordered_set<int>;

bool isDisjoint(const Set& a, const Set& b) {
    const Set& small = a.size() < b.size() ? a : b;
    const Set& big = a.size() < b.size() ? b : a;
    for (int x : small) if (big.count(x)) return false;
    return true;
}
bool isSubset(const Set& a, const Set& b) {          // a ⊆ b
    if (a.size() > b.size()) return false;
    for (int x : a) if (!b.count(x)) return false;
    return true;
}
bool isProperSubset(const Set& a, const Set& b) { return a.size() < b.size() && isSubset(a, b); }
const char* B(bool x) { return x ? "true" : "false"; }

int main() {
    Set s1 = {10, 20}, s2 = {40, 30, 50}, one = {10};
    cout << "s1 disjoint s2: " << B(isDisjoint(s1, s2)) << "\n";
    cout << "s2 disjoint s1: " << B(isDisjoint(s2, s1)) << "\n";
    cout << "s1 disjoint s1: " << B(isDisjoint(s1, s1)) << "\n";
    cout << "s1 <= s2: " << B(isSubset(s1, s2)) << "\n";
    cout << "s1 < s2: " << B(isProperSubset(s1, s2)) << "\n";
    cout << "s1 >= s2: " << B(isSubset(s2, s1)) << "\n";
    cout << "s1 > s2: " << B(isProperSubset(s2, s1)) << "\n";
    cout << "{10} <= s1: " << B(isSubset(one, s1)) << "\n";
    cout << "{10} < s1: " << B(isProperSubset(one, s1)) << "\n";
    cout << "s1 <= s1: " << B(isSubset(s1, s1)) << "\n";
    cout << "s1 < s1: " << B(isProperSubset(s1, s1)) << "\n";
}
Java
import java.util.*;

public class Main {
    static boolean isDisjoint(Set<Integer> a, Set<Integer> b) { return Collections.disjoint(a, b); }
    static boolean isSubset(Set<Integer> a, Set<Integer> b) { return b.containsAll(a); }     // a ⊆ b
    static boolean isProperSubset(Set<Integer> a, Set<Integer> b) { return a.size() < b.size() && b.containsAll(a); }

    public static void main(String[] args) {
        Set<Integer> s1 = Set.of(10, 20), s2 = Set.of(40, 30, 50), one = Set.of(10);
        System.out.println("s1 disjoint s2: " + isDisjoint(s1, s2));
        System.out.println("s2 disjoint s1: " + isDisjoint(s2, s1));
        System.out.println("s1 disjoint s1: " + isDisjoint(s1, s1));
        System.out.println("s1 <= s2: " + isSubset(s1, s2));
        System.out.println("s1 < s2: " + isProperSubset(s1, s2));
        System.out.println("s1 >= s2: " + isSubset(s2, s1));
        System.out.println("s1 > s2: " + isProperSubset(s2, s1));
        System.out.println("{10} <= s1: " + isSubset(one, s1));
        System.out.println("{10} < s1: " + isProperSubset(one, s1));
        System.out.println("s1 <= s1: " + isSubset(s1, s1));
        System.out.println("s1 < s1: " + isProperSubset(s1, s1));
    }
}
JavaScript
const s1 = new Set([10, 20]);
const s2 = new Set([40, 30, 50]);
const one = new Set([10]);
const properSubset = (a, b) => a.size < b.size && a.isSubsetOf(b);

console.log("s1 disjoint s2:", s1.isDisjointFrom(s2));     // ES2025 Set methods
console.log("s2 disjoint s1:", s2.isDisjointFrom(s1));
console.log("s1 disjoint s1:", s1.isDisjointFrom(s1));
console.log("s1 <= s2:", s1.isSubsetOf(s2));
console.log("s1 < s2:", properSubset(s1, s2));
console.log("s1 >= s2:", s1.isSupersetOf(s2));
console.log("s1 > s2:", properSubset(s2, s1));
console.log("{10} <= s1:", one.isSubsetOf(s1));
console.log("{10} < s1:", properSubset(one, s1));
console.log("s1 <= s1:", s1.isSubsetOf(s1));
console.log("s1 < s1:", properSubset(s1, s1));
Output
s1 disjoint s2: true
s2 disjoint s1: true
s1 disjoint s1: false
s1 <= s2: false
s1 < s2: false
s1 >= s2: false
s1 > s2: false
{10} <= s1: true
{10} < s1: true
s1 <= s1: true
s1 < s1: false

Time O(min(|a|, |b|)) · Space O(1)

Iteration order

ContainerIteration order
Python setArbitrary (small ints often look sorted because hash(i) == i — do not rely on it); string hashes are randomised per process
C++ unordered_setArbitrary, implementation defined
Java HashSetArbitrary; LinkedHashSet = insertion order; TreeSet = sorted
JavaScript SetInsertion order, guaranteed by the language specification

Typical uses

Problem: count distinct elements in an array

Python
def count_distinct(arr):
    return len(set(arr))                  # duplicates collapse on insert

for arr in ([10, 20, 20, 10, 30], [5, 5, 5], [1, 2, 3, 4]):
    print(*arr, "→", count_distinct(arr), "distinct")
C++
#include <iostream>
#include <vector>
#include <unordered_set>
using namespace std;

int countDistinct(const vector<int>& arr) {
    unordered_set<int> seen(arr.begin(), arr.end());   // duplicates collapse on insert
    return (int)seen.size();
}

int main() {
    vector<vector<int>> tests = {{10, 20, 20, 10, 30}, {5, 5, 5}, {1, 2, 3, 4}};
    for (const auto& arr : tests) {
        for (int x : arr) cout << x << " ";
        cout << "→ " << countDistinct(arr) << " distinct\n";
    }
}
Java
import java.util.*;

public class Main {
    static int countDistinct(int[] arr) {
        Set<Integer> seen = new HashSet<>();
        for (int x : arr) seen.add(x);              // duplicates collapse on insert
        return seen.size();
    }

    public static void main(String[] args) {
        int[][] tests = {{10, 20, 20, 10, 30}, {5, 5, 5}, {1, 2, 3, 4}};
        for (int[] arr : tests) {
            StringBuilder sb = new StringBuilder();
            for (int x : arr) sb.append(x).append(" ");
            System.out.println(sb + "→ " + countDistinct(arr) + " distinct");
        }
    }
}
JavaScript
const countDistinct = (arr) => new Set(arr).size;   // duplicates collapse on insert

for (const arr of [[10, 20, 20, 10, 30], [5, 5, 5], [1, 2, 3, 4]])
    console.log(arr.join(" "), "→", countDistinct(arr), "distinct");
Output
10 20 20 10 30 → 3 distinct
5 5 5 → 1 distinct
1 2 3 4 → 4 distinct

Time O(n) average · Space O(n)

Problem: do two arrays have a common element?

Python
def common_element(a, b):
    seen = set(a)                          # O(len(a))
    for x in b:                            # O(len(b)) membership tests
        if x in seen:
            return x
    return None

for a, b in (([3, 8, 12, 5], [7, 1, 12]), ([1, 2], [3, 4])):
    found = common_element(a, b)
    result = f"Yes ({found})" if found is not None else "No"
    print(f"[{' '.join(map(str, a))}] and [{' '.join(map(str, b))}] →", result)
C++
#include <iostream>
#include <vector>
#include <unordered_set>
#include <string>
using namespace std;

bool commonElement(const vector<int>& a, const vector<int>& b, int& found) {
    unordered_set<int> seen(a.begin(), a.end());   // O(len(a))
    for (int x : b)                                // O(len(b)) membership tests
        if (seen.count(x)) { found = x; return true; }
    return false;
}

string join(const vector<int>& v) {
    string s = "[";
    for (int i = 0; i < (int)v.size(); i++) s += (i ? " " : "") + to_string(v[i]);
    return s + "]";
}

int main() {
    vector<pair<vector<int>, vector<int>>> tests = {{{3, 8, 12, 5}, {7, 1, 12}}, {{1, 2}, {3, 4}}};
    for (auto& [a, b] : tests) {
        int found;
        bool ok = commonElement(a, b, found);
        cout << join(a) << " and " << join(b) << " → " << (ok ? "Yes (" + to_string(found) + ")" : "No") << "\n";
    }
}
Java
import java.util.*;

public class Main {
    static Integer commonElement(int[] a, int[] b) {
        Set<Integer> seen = new HashSet<>();
        for (int x : a) seen.add(x);                   // O(len(a))
        for (int x : b) if (seen.contains(x)) return x; // O(len(b)) membership tests
        return null;
    }

    static String join(int[] v) {
        StringBuilder sb = new StringBuilder("[");
        for (int i = 0; i < v.length; i++) sb.append(i > 0 ? " " : "").append(v[i]);
        return sb + "]";
    }

    public static void main(String[] args) {
        int[][][] tests = {{{3, 8, 12, 5}, {7, 1, 12}}, {{1, 2}, {3, 4}}};
        for (int[][] t : tests) {
            Integer found = commonElement(t[0], t[1]);
            System.out.println(join(t[0]) + " and " + join(t[1]) + " → " + (found != null ? "Yes (" + found + ")" : "No"));
        }
    }
}
JavaScript
function commonElement(a, b) {
    const seen = new Set(a);                 // O(len(a))
    for (const x of b)                       // O(len(b)) membership tests
        if (seen.has(x)) return x;
    return null;
}

const join = (v) => "[" + v.join(" ") + "]";
for (const [a, b] of [[[3, 8, 12, 5], [7, 1, 12]], [[1, 2], [3, 4]]]) {
    const found = commonElement(a, b);
    console.log(join(a), "and", join(b), "→", found !== null ? `Yes (${found})` : "No");
}
Output
[3 8 12 5] and [7 1 12] → Yes (12)
[1 2] and [3 4] → No

Time O(n + m) average · Space O(min(n, m))

Interview Q&A

How is a set different from a list / array?

Why must set elements be hashable / immutable?

What are the complexities of union, intersection and difference?

When would you pick a TreeSet / std::set over a hash set?

How do you remove duplicates from a list while keeping the original order?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report