Back to Blog
Java

Java HashSet Size: Using size() Correctly

java hashset size: Learn how to get the number of elements in a Java HashSet using size(), its constant-time behavior, and common pitfalls to avoid.

JavaHashSetCollectionssize() methodData structures
Illustration of a Java HashSet with a size counter showing the element count

When working with Java collections, one of the most common operations is determining how many elements a collection contains. For a HashSet, the size() method provides this count directly. The java hashset size is obtained with a simple call: set.size(). This method returns an int representing the number of elements currently stored in the set. Because HashSet is backed by a hash table, the size() method runs in constant time, O(1), regardless of how many elements the set holds.

Using size() to Get the Element Count

The size() method is defined in the java.util.Collection interface, which HashSet implements. Here is a minimal example:

import java.util.HashSet; import java.util.Set; public class HashSetSizeExample { public static void main(String[] args) { Set<String> fruits = new HashSet<>(); fruits.add("apple"); fruits.add("banana"); fruits.add("cherry"); int count = fruits.size(); System.out.println("Number of fruits: " + count); // Output: 3 } }

The size() method returns the number of key-value mappings, or in the case of a HashSet, the number of elements. It does not count duplicates because a HashSet does not allow duplicate elements. If you attempt to add an element that already exists, the set is unchanged, and size() reflects that.

What size() Returns and Its Contract

The size() method returns an int. This means the maximum size that can be represented is Integer.MAX_VALUE (2,147,483,647). In practice, a HashSet will run out of memory long before reaching that limit, but it is worth noting that the return type is a signed 32-bit integer. The method returns the current number of elements in the set at the moment it is called. It does not reflect any pending modifications or the set's capacity.

The contract for size() is simple: it returns the number of elements in the collection. For a HashSet, this is the count of distinct elements. Because HashSet relies on hashCode() and equals() to determine uniqueness, the size() value is directly tied to how those methods are implemented for the stored objects.

Time Complexity of HashSet.size()

One of the reasons HashSet is popular for membership tests is its constant-time average complexity for add, remove, and contains. The size() method is equally efficient. Internally, HashSet maintains a counter that is incremented on every successful add and decremented on every successful remove. Therefore, size() simply returns this counter, making it an O(1) operation. This is in contrast to methods like stream().count() or iterating through the set manually, which are O(n) and should be avoided when you only need the count.

Because size() is O(1), you can call it repeatedly in a loop or in multiple places without worrying about performance degradation. For example, if you need to check whether a set has reached a certain size before adding more elements, you can safely call size() in each iteration.

Common Mistakes When Working with HashSet Size

A common mistake is confusing size() with the capacity of the underlying hash table. HashSet does not expose a capacity method like ArrayList does with ensureCapacity(). The size() method always reflects the number of elements, not the number of buckets or the load factor. The load factor only affects when the set resizes internally; it does not change the value returned by size().

Another mistake is using size() to check for emptiness. While set.size() == 0 works, it is less readable than set.isEmpty(). The isEmpty() method is also O(1) and is the idiomatic way to test for an empty collection. Using size() for this purpose is not wrong, but it is less expressive.

A more subtle issue arises when you modify a HashSet while iterating over it. If you call size() inside an iteration that also modifies the set, you may get a ConcurrentModificationException if you try to modify the set directly. The size() call itself is safe, but the modification is not. The solution is to use an Iterator's remove() method or collect changes separately.

Checking for Empty vs. Zero Size

As mentioned, isEmpty() is the preferred way to check if a HashSet has no elements. Here is an example:

Set<String> set = new HashSet<>(); if (set.isEmpty()) { System.out.println("Set is empty"); }

Using isEmpty() makes the intent clear and avoids the extra comparison. Both isEmpty() and size() == 0 are O(1), but isEmpty() is more readable. In code reviews, isEmpty() is generally preferred.

Thread Safety and Size in Concurrent Scenarios

HashSet is not thread-safe. If multiple threads modify the same HashSet without external synchronization, the size() method may return a stale value. The counter used internally is not atomic, and concurrent writes can lead to an inaccurate count. For example, if two threads add elements simultaneously, the internal counter might not reflect both additions because the increment is not atomic.

If you need a thread-safe set with reliable size information, consider using ConcurrentHashMap.newKeySet() or Collections.synchronizedSet(new HashSet<>()). However, even with a synchronized set, the size() method will reflect the state at the moment of the call, but it is not a snapshot of a consistent state if other threads are still modifying the set. For a truly consistent count, you would need to synchronize on the set while reading size() and while performing modifications.

Performance Considerations for Large HashSets

For very large HashSet instances, size() remains O(1), so there is no performance penalty for calling it. However, the memory footprint of the set itself grows with the number of elements and the load factor. The size() value does not directly indicate memory usage; a set with a high load factor may have many empty buckets, but size() only counts actual elements.

When you need to iterate over all elements, you might use size() to preallocate an array or list of the correct capacity. For example:

Set<String> set = ...; List<String> list = new ArrayList<>(set.size()); list.addAll(set);

This avoids unnecessary resizing of the ArrayList because the initial capacity is set to the exact number of elements. This is a practical use of size() beyond simply printing the count.

When to Use size() vs. Other Collection Methods

The size() method is part of the Collection interface, so it behaves consistently across List, Set, and Queue implementations. For a HashSet, size() is the standard way to get the element count. If you are working with a Map, you would use map.size() instead. The semantics are the same: the number of key-value pairs.

In summary, HashSet.size() is a simple, constant-time operation that gives you the number of elements in the set. It is reliable for single-threaded usage and is the idiomatic way to retrieve the count. For emptiness checks, prefer isEmpty(). For concurrent scenarios, be aware of the lack of thread safety and use appropriate synchronization.

java hashset size: Practical Usage and Code Examples | RYUSLOG DEV