Set

Hi everyone ✋

In this tutorial we will talk about Set

So, What is Set actually?

Set is an associative container of C++ STL that stores only unique elements. Each element inside a set is a key — there are no associated values (that is the job of map). The elements are always kept sorted in ascending order automatically.

Think of set as map without the values — just the keys. Or think of it as a sorted container that silently drops duplicates for us.

Since set stores only unique keys, it does not have the [ ] subscript operator or the at() method. Set is not about looking up values by keys — it is about answering the question “is this element present?” or iterating through the elements in sorted order.

Set’s underlying data structure is a Self-Balancing Binary Search Tree (usually a Red-Black Tree). That is why all its major operations — insertion, deletion, search — take O(log n) time, and that is also why the elements stay sorted automatically.

Header File Inclusion for set

#include <set>

Declaration & Initialization

Declaring an empty set —


set <int> numbers;

The set is empty right now.

Declaring a set with values —

From C++11 onwards, we can initialize a set with values using curly braces —


set <int> numbers = { 50, 20, 40, 10, 30 };

After Initialization numbers will look like this —

 

10 20 30 40 50

 

Notice that even though we inserted the values in the order 50, 20, 40, 10, 30, set has sorted them in ascending order.

Declaring a set with duplicate values —

If we try to initialize a set with duplicate values, the duplicates are silently dropped


set <int> numbers = { 10, 20, 30, 20, 40, 10, 50 };

After Initialization numbers will look like this —

 

10 20 30 40 50

 

The duplicate 10 and 20 were ignored. This is one of the most common uses of set — to deduplicate a collection of values.

Declaring sets of different types —

Element type can be anything that supports the < operator —


set <double> scores;
set <string> nameList;
set <char> letters;
set <pair<int, int> > points;

Declaring a set in descending order —

By default, set stores elements in ascending order. If we want elements in descending order, we can pass greater<> as the second template parameter —


set <int, greater<int> > reverseSet = { 10, 20, 30, 40, 50 };

After Initialization reverseSet will look like this —

 

50 40 30 20 10

 

Insertion

Insertion using insert() method

The main way to add elements to a set is the insert() method —


set <int> numbers;

numbers.insert(20);
numbers.insert(50);
numbers.insert(10);
numbers.insert(40);
numbers.insert(30);

After Insertion numbers will look like this —

 

10 20 30 40 50

 

Inserting a duplicate value

If we try to insert a value that already exists, the insertion is silently ignored —


numbers.insert(30);

numbers will still look like this —

 

10 20 30 40 50

 

💡 Quick tip on insert() return value — insert() actually returns a pair<iterator, bool>. The bool tells us whether the insertion actually happened (true) or the value was already there (false). This is useful when we want to check for duplicates during insertion.


auto result = numbers.insert(30);

if (result.second)
{
    cout << "Inserted successfully" << endl;
}
else
{
    cout << "Already exists" << endl;
}

Insertion using emplace() method

emplace() constructs the element in place, avoiding an extra copy —


numbers.emplace(60);
numbers.emplace(70);

Inserting a range of values

We can also insert a range of values from another container —


vector <int> extra = { 80, 90, 100 };

numbers.insert(extra.begin(), extra.end());

Accessing the element

Set does not support [ ] or at(). Access happens through iterators returned by find(), or by iterating through the set directly.

find() method

find() returns an iterator to the element if it exists, otherwise it returns end() —


auto it = numbers.find(30);

if (it != numbers.end())
{
    cout << "Found " << *it << endl;
}
else
{
    cout << "Not found" << endl;
}

Output will be – Found 30

Checking if an element exists

count() method

Since set elements are unique, count() returns either 0 (not present) or 1 (present) —


if (numbers.count(30))
{
    cout << "30 is in the set" << endl;
}

contains() method (C++20)

From C++20 onwards, we have a cleaner way —


if (numbers.contains(30))
{
    cout << "30 is in the set" << endl;
}

Iterating through a set

Range-based for loop

The cleanest way to iterate through a set is using a range-based for loop —


for (auto &value : numbers)
{
    cout << value << " ";
}

Output will be – 10 20 30 40 50 60 70 80 90 100

Using iterator

We can also iterate using iterators directly —


set <int>::iterator it;

for (it = numbers.begin(); it != numbers.end(); it++)
{
    cout << *it << " ";
}

💡 Note on iteration order — No matter the order in which we inserted the values, set always iterates them in sorted order (ascending by default). That is one of the most powerful features of set.

Deletion

Deletion using erase() with value

We can delete a specific element by passing the value directly to erase() —


numbers.erase(30);

After deletion, the element 30 will be removed from the set.

Deletion using erase() with iterator

We can also delete using an iterator —


auto it = numbers.find(40);

if (it != numbers.end())
{
    numbers.erase(it);
}

Deletion using erase() with range

We can delete a range of elements by passing the start and end iterators —


// removing all elements from 60 onwards
numbers.erase(numbers.find(60), numbers.end());

clear() method

If we want to remove all elements of the set, we can use clear() —


numbers.clear();

After clear(), numbers will be empty.

Size and Empty


cout << numbers.size() << endl;

if (numbers.empty())
{
    cout << "Set is empty" << endl;
}

size() returns the number of elements, and empty() returns true if the set has no elements.

Swapping

swap() method

STL set has a method named swap(), which swaps all the elements between two sets —


set <int> setOne = { 1, 2, 3 };
set <int> setTwo = { 10, 20, 30, 40, 50 };

setOne will look like this —

 

1 2 3

 

setTwo will look like this —

 

10 20 30 40 50

 

Now, let’s swap the elements of two sets —


// swapping values between two sets
setOne.swap(setTwo);

After Swapping setOne will look like this —

 

10 20 30 40 50

 

After Swapping setTwo will look like this —

 

1 2 3

 

lower_bound() and upper_bound()

Since set elements are stored in sorted order, we can use lower_bound() and upper_bound() just like we do for sorted vectors.

lower_bound(value) — returns an iterator to the first element that is not less than the given value.

upper_bound(value) — returns an iterator to the first element that is strictly greater than the given value.


set <int> numbers = { 10, 20, 30, 40, 50 };

auto lb = numbers.lower_bound(30);
auto ub = numbers.upper_bound(30);

cout << *lb << endl;
cout << *ub << endl;

Output will be —


30
40

These are handy for range queries — for example, finding all values in the set between two bounds.

Accessing the smallest and largest element

Since set is always sorted, the smallest element is at begin() and the largest element is at prev(end()) or rbegin()


set <int> numbers = { 40, 10, 50, 20, 30 };

// smallest element
cout << *numbers.begin() << endl;

// largest element
cout << *numbers.rbegin() << endl;

Output will be —


10
50

💡 Quick tip on min/max access — Unlike priority_queue (which only gives us the top element), set gives us O(log n) access to both the smallest and largest elements. This makes set a great choice when we need to repeatedly query both extremes of a collection.

Set vs unordered_set

Just like map has an unordered_map cousin, set has an unordered_set cousin. This is a common confusion, so it is worth a quick note —

 

Property set unordered_set
Underlying data structure Red-Black Tree Hash Table
Order of elements Sorted No specific order
Insertion / Deletion / Search O(log n) O(1) average, O(n) worst
lower_bound() / upper_bound() Available Not available
Use when We need sorted elements or ordered traversal We just need fast lookup, order does not matter

 

Time Complexity

 

Operation Description Time Complexity
insert() Insert an element O(log n)
emplace() Construct and insert an element in place O(log n)
erase() Remove an element by value or iterator O(log n)
find() Find an element O(log n)
count() Check if an element exists (returns 0 or 1) O(log n)
contains() Check if an element exists (C++20) O(log n)
lower_bound() Iterator to first value not less than given value O(log n)
upper_bound() Iterator to first value greater than given value O(log n)
begin() / end() Iterators to the beginning and end O(1)
rbegin() / rend() Reverse iterators (access largest, smallest) O(1)
size() Returns the number of elements O(1)
empty() Returns true if set is empty O(1)
clear() Removes all elements O(n)
swap() Swaps contents of two sets O(1)

 

Applications

        • Removing duplicates from a collection of values
        • Checking if an element has been seen before (visited-set in BFS/DFS)
        • Maintaining a sorted collection that supports fast insertions and deletions
        • Finding the k-th smallest or largest element via iterator advance
        • Range queries with lower_bound() and upper_bound()
        • Sliding window problems where we need both min and max of the current window
        • Implementing set operations like union, intersection, and difference (with std::set_union, std::set_intersection, std::set_difference)
        • Storing unique identifiers like user IDs, words in a dictionary, or distinct IP addresses

In the next tutorial we will talk about another useful STL container.

Until then
Happy Coding 💻 🎵

Leave a Comment

Your email address will not be published. Required fields are marked *