LearnFrenzy
Sign in Take a free test

STL: Set, Map and Hash Tables · Lesson 20 of 33

set and multiset in C++ STL

5 min read Updated Nov 7, 2026 Saurabh Samir

A std::set keeps unique values in sorted order and finds, inserts and removes any of them in O(log n). That makes it perfect for "have I seen this before?", "what's the smallest value above x?" and keeping a sorted collection that changes – questions that come up constantly in DSA problems.

In this lesson

  • Inserting, finding and erasing
  • lower_bound and upper_bound
  • Custom ordering
  • multiset for duplicates

The basics

#include <iostream>
#include <set>

int main() {
    std::set<int> s{50, 20, 40, 20, 10};         // duplicates are ignored, values sorted
    s.insert(30);
    auto [it, inserted] = s.insert(40);           // insert returns {iterator, was it new?}
    std::cout << "40 inserted again? " << std::boolalpha << inserted << '\n';

    for (int x : s) std::cout << x << ' ';        // always in sorted order
    std::cout << "| size " << s.size() << '\n';

    std::cout << "has 30? " << s.contains(30) << ", has 35? " << s.contains(35) << '\n';   // C++20
    s.erase(20);
    std::cout << "smallest " << *s.begin() << ", largest " << *s.rbegin() << '\n';
    return 0;
}

Output

40 inserted again? false
10 20 30 40 50 | size 5
has 30? true, has 35? false
smallest 10, largest 50

std::set is a balanced binary search tree (red-black tree)

30

  • left subtree10 – values smaller than 30
  • right subtree40, 50 – values larger than 30

Because the tree stays balanced, its height is about log₂ n, so every operation is O(log n) – about 20 steps for a million elements. Iterating visits the values in sorted order.

lower_bound and upper_bound

#include <iostream>
#include <set>

int main() {
    std::set<int> prices{99, 149, 299, 499, 999};

    auto a = prices.lower_bound(300);        // first value >= 300
    auto b = prices.upper_bound(499);        // first value > 499
    std::cout << "first >= 300: " << *a << ", first > 499: " << *b << '\n';

    auto c = prices.lower_bound(250);
    if (c != prices.begin()) std::cout << "largest below 250: " << *std::prev(c) << '\n';

    if (prices.lower_bound(2000) == prices.end()) std::cout << "nothing costs 2000 or more\n";

    int count = 0;                           // values in the range [100, 500]
    for (auto it = prices.lower_bound(100); it != prices.upper_bound(500); ++it) count++;
    std::cout << count << " prices between 100 and 500\n";
    return 0;
}

Output

first >= 300: 499, first > 499: 999
largest below 250: 149
nothing costs 2000 or more
3 prices between 100 and 500

Use the member functions

s.lower_bound(x) is O(log n). The general algorithm std::lower_bound(s.begin(), s.end(), x) also compiles on a set, but set iterators aren't random access, so it runs in O(n) – a classic "Time Limit Exceeded" trap.

Custom ordering

#include <functional>
#include <iostream>
#include <set>
#include <string>

struct ByLength {
    bool operator()(const std::string& a, const std::string& b) const {
        return a.size() != b.size() ? a.size() < b.size() : a < b;   // length, then alphabetical
    }
};

int main() {
    std::set<int, std::greater<int>> desc{3, 9, 1, 7};          // largest first
    for (int x : desc) std::cout << x << ' ';
    std::cout << '\n';

    std::set<std::string, ByLength> words{"stack", "map", "set", "vector", "queue"};
    for (const auto& w : words) std::cout << w << ' ';
    std::cout << '\n';
    return 0;
}

Output

9 7 3 1 
map set queue stack vector 

The set treats two values as the same when neither comes before the other. With a comparator that only compared length, "map" and "set" would be "equal" and one would be dropped – so always break ties.

multiset: sorted with duplicates

#include <iostream>
#include <set>

int main() {
    std::multiset<int> scores{70, 85, 70, 90, 85, 70};
    std::cout << "70 appears " << scores.count(70) << " times\n";

    scores.erase(scores.find(70));          // erase ONE 70
    std::cout << "after erasing one: " << scores.count(70) << '\n';
    scores.erase(85);                       // erase ALL 85s
    for (int s : scores) std::cout << s << ' ';
    std::cout << '\n';
    std::cout << "median-ish: " << *std::next(scores.begin(), scores.size() / 2) << '\n';
    return 0;
}

Output

70 appears 3 times
after erasing one: 2
70 70 90 
median-ish: 70

A classic multiset bug

ms.erase(value) removes every copy of the value. To remove just one, erase an iterator: ms.erase(ms.find(value)) (after checking it isn't end()).

Practice

Try it yourself

  1. Read a list of numbers and print the distinct values in sorted order and how many there were.

    Show solution
    #include <iostream>
    #include <set>
    #include <vector>
    
    int main() {
        std::vector<int> data{4, 8, 4, 15, 16, 8, 23, 42, 15};
        std::set<int> distinct(data.begin(), data.end());
        for (int x : distinct) std::cout << x << ' ';
        std::cout << "| " << distinct.size() << " distinct\n";
        return 0;
    }

    Output

    4 8 15 16 23 42 | 6 distinct

Quick check

What's the time complexity of set insert and find?

O(log n).

lower_bound vs upper_bound?

lower_bound: first element ≥ x; upper_bound: first element > x.

How do you erase only one copy from a multiset?

ms.erase(ms.find(x)).

Summary

  • set = sorted unique values in a balanced tree: O(log n) insert, find, erase.
  • Member lower_bound/upper_bound answer "next larger/smaller" queries in O(log n).
  • Custom comparators must break ties; multiset allows duplicates (erase by iterator for one).

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Popular