NSCountedSet: Counting Membership

Understand NSCountedSet as a multiset (bag) that tracks how many times each object has been added, how it differs from NSSet's no-op duplicate handling, and how it serves as a native alternative to a dictionary-of-counts for frequency-counting problems.

Every set variant so far has treated a duplicate add as either forbidden (silently ignored, in the case of NSSet and NSOrderedSet) — the collection only ever answers "is this object present?", never "how many times was it added?" NSCountedSet, Foundation's third set variant, changes that rule: it's a set that actually remembers how many times each distinct object was added, making it what computer science calls a multiset or bag. This lesson covers NSCountedSet's counting mechanics, contrasts its addObject: behavior directly against plain NSSet, and works through the frequency-counting problems it's built to solve.

A set that counts instead of ignoring

NSCountedSet is a concrete subclass of NSMutableSet — it inherits everything you already know about NSSet and NSMutableSet (uniqueness determined via isEqual:/hash, containsObject:, unordered enumeration) but changes the meaning of a duplicate add in one crucial way: instead of the second, third, or fourth addObject: call for an already-present object being a silent no-op, NSCountedSet increments an internal counter for that object each time.

From the outside, an NSCountedSet still behaves like a set for membership purposes — enumerating it still visits each distinct object exactly once, and containsObject: still just answers YES or NO. What's different is that the set now also remembers, per object, how many times addObject: was called for it, information a plain NSSet discards entirely.

The method that surfaces this extra information is countForObject:, which takes an object and returns an NSUInteger — the number of times that object has been added to the set (net of any removeObject: calls), or 0 if the object isn't present at all. This single method is what turns NSCountedSet from "just another set" into a genuinely different tool.

addObject: on NSCountedSet versus plain NSSet

This contrast is worth stating as plainly as possible, because it's the crux of the whole lesson: on a plain NSMutableSet, calling addObject: with an object that's already present does nothing at all — the set's count (as in, its total number of distinct elements) stays exactly the same, and there is no record anywhere that the add was even attempted twice. On an NSCountedSet, calling addObject: with an object already present increments that object's internal tally, retrievable via countForObject:, even though the set's count property (the number of distinct elements) still doesn't change, because count on any NSSet subclass always reports distinct element count, never total additions.

This distinction — distinct-element count (count) versus per-object occurrence count (countForObject:) — is the thing to internalize. If you add the string @"apple" to an NSCountedSet five times, [countedSet count] still reports 1 (there is exactly one distinct element, "apple"), while [countedSet countForObject:@"apple"] reports 5 (it was added five times). Confusing the two is a natural early mistake.

removeObject: decrements the count for a given object by one rather than deleting it outright; the object is only actually removed from the set entirely (and countForObject: only then returns 0) once its tally has been decremented all the way down to zero. This mirrors how a real physical bag of marbles works: taking one red marble out of a bag that has three doesn't empty the bag of red marbles, it just leaves two.

Frequency counting: the classic use case

The textbook use case for NSCountedSet is frequency counting: given a stream of items, determine how many times each distinct item occurred. Word-occurrence counting in a block of text is the canonical example — split the text into words, add each word to an NSCountedSet as you encounter it, and once you're done, countForObject: gives you each word's frequency directly, with no manual bookkeeping required.

Vote tallying is another natural fit: as each vote comes in (say, a candidate's name), add it to an NSCountedSet; countForObject: for any candidate at any point gives you their running total, and you can find the current leader by asking for the maximum countForObject: across all distinct candidates currently in the set.

Without NSCountedSet, the conventional way to solve either of these problems is a "dictionary of counts" pattern: an NSMutableDictionary mapping each distinct item to an NSNumber holding its running count, manually incrementing that NSNumber (which, being immutable, means constructing a brand-new NSNumber and re-storing it) every time the item recurs. NSCountedSet solves the exact same problem natively — one addObject: call per occurrence, one countForObject: call per query — without you ever having to manage the counting dictionary by hand.

Comparing NSCountedSet to the dictionary-of-counts pattern

Both approaches ultimately store the same information: a mapping from distinct item to how many times it occurred. The dictionary-of-counts approach is more explicit about that mapping and is a completely reasonable, common pattern you'll see throughout Objective-C code, especially when you need to do more with the counts than simply retrieve them (like sorting all counts, or storing additional metadata alongside each count). NSCountedSet is the more direct tool specifically when the operation you're performing really is "add an occurrence" / "how many occurrences so far" and nothing more — it collapses the increment-a-stored-NSNumber dance into a single addObject: call.

One tradeoff to be aware of: because NSCountedSet is a subclass of NSMutableSet, it inherits set-style enumeration — iterating it with a for-in loop or fast enumeration gives you each distinct object exactly once (not once per occurrence), and you'd pair that with countForObject: inside the loop to get both the item and its tally together. This is usually exactly what you want for reporting results ("word: count" pairs), but it's worth knowing you don't get the raw occurrence stream back out — only the object plus its final tally.

When NSSet's counting behavior is actually what you need

It's worth explicitly naming the mistake this invites: reaching for a plain NSSet (or NSMutableSet) when what the problem actually calls for is tracking occurrence counts. If your code ever finds itself needing to know "how many times has this been added" and you're using a plain NSSet, you'll find there's simply no way to ask it that question — a plain set only ever remembers "was it added at all," and every fact about how many times gets thrown away the moment a duplicate add becomes a no-op. Recognizing that need up front and reaching for NSCountedSet from the start avoids having to retrofit a counting mechanism later.

Code Examples

Each word is added to the counted set exactly once per occurrence in the source array. Unlike a plain NSSet, adding "the" a second and third time doesn't get ignored — it increments "the"'s tally to 3. Enumerating wordCounts still visits each distinct word exactly once (three distinct words: the, quick, fox), and countForObject: retrieves each word's occurrence count separately. Expected result: the: 3 quick: 1 fox: 2 Distinct words: 3 Enumeration order over an NSCountedSet is unspecified, just as with any set, so the three log lines could print in any order — only the counts themselves (3, 1, 2) are guaranteed.

[wordCounts addObject:word]; — each call for a repeated word increments its tally rather than being a no-op, unlike plain NSSet.

Rather than maintaining a hand-written NSMutableDictionary of candidate-to-NSNumber-count, each incoming vote is simply added to an NSCountedSet. Finding the leader is then a matter of scanning the distinct candidates (enumeration visits each once) and comparing their countForObject: tallies. Expected result: Current leader: Alice with 3 votes

NSUInteger votes = [tally countForObject:candidate]; retrieves how many times this specific candidate was added, independent of the other candidates.

This directly contrasts the two classes side by side. Both plainSet.count and countedSet.count report 1, because both collections have exactly one distinct element — count never reports total additions on any NSSet subclass. Only countForObject:, unique to NSCountedSet, reveals that "x" was actually added three times. removeObject: then decrements the tally by one (down to 2) rather than deleting the element outright. Expected result: Plain set count: 1 Counted set count: 1 Counted set countForObject: 3 After one removal, countForObject: 2

[countedSet countForObject:@"x"] returns 3 even though countedSet.count returns 1 — distinct-element count and occurrence count are different questions.

NSArray *words = @[@"the", @"quick", @"fox", @"the", @"fox", @"the"];
NSCountedSet *wordCounts = [NSCountedSet set];

for (NSString *word in words) {
    [wordCounts addObject:word];
}

for (NSString *word in wordCounts) {
    NSLog(@"%@: %lu", word, (unsigned long)[wordCounts countForObject:word]);
}

NSLog(@"Distinct words: %lu", (unsigned long)wordCounts.count);
NSArray *incomingVotes = @[@"Alice", @"Bob", @"Alice", @"Carol", @"Alice", @"Bob"];
NSCountedSet *tally = [NSCountedSet set];

for (NSString *candidate in incomingVotes) {
    [tally addObject:candidate];
}

NSString *leader = nil;
NSUInteger leaderVotes = 0;
for (NSString *candidate in tally) {
    NSUInteger votes = [tally countForObject:candidate];
    if (votes > leaderVotes) {
        leaderVotes = votes;
        leader = candidate;
    }
}

NSLog(@"Current leader: %@ with %lu votes", leader, (unsigned long)leaderVotes);
NSMutableSet *plainSet = [NSMutableSet set];
[plainSet addObject:@"x"];
[plainSet addObject:@"x"];
[plainSet addObject:@"x"];
NSLog(@"Plain set count: %lu", (unsigned long)plainSet.count); // distinct elements only

NSCountedSet *countedSet = [NSCountedSet set];
[countedSet addObject:@"x"];
[countedSet addObject:@"x"];
[countedSet addObject:@"x"];
NSLog(@"Counted set count: %lu", (unsigned long)countedSet.count); // still distinct elements only
NSLog(@"Counted set countForObject: %lu", (unsigned long)[countedSet countForObject:@"x"]); // occurrences

[countedSet removeObject:@"x"];
NSLog(@"After one removal, countForObject: %lu", (unsigned long)[countedSet countForObject:@"x"]);

NSSet vs NSCountedSet on repeated adds

Both end up with exactly one distinct element, but NSCountedSet alone remembers that the add happened three times.

NSSet:          addObject:@"x" x3  ──▶  {"x"}            (no-op after first add)
NSCountedSet:   addObject:@"x" x3  ──▶  {"x"} , countForObject:@"x" = 3

Important Rules

NSCountedSet is a concrete subclass of NSMutableSet that tracks how many times each object was added — a multiset (bag), not just a plain set.

count still reports the number of distinct elements; countForObject: is the method that reports how many times a specific object was added.

addObject: on an already-present object increments its tally on NSCountedSet, in direct contrast to plain NSSet, where the same call is a silent no-op.

removeObject: decrements an object's tally by one; the object only disappears from the set entirely once its tally reaches zero.

NSCountedSet natively solves frequency-counting problems (word counts, vote tallies) that would otherwise require a hand-maintained dictionary-of-counts.

Tallying without hand-rolled dictionaries

NSCountedSet shows up wherever an app needs to tally occurrences of discrete items without the ceremony of managing an NSMutableDictionary of NSNumbers by hand: counting how many times each error code appeared in a log session, tracking how many times each emoji reaction was tapped on a post, tallying how many times each product SKU appears across a shopping cart's line items before checkout, or counting word frequency for a simple text-analysis feature. Anywhere the underlying question is "how many times has X occurred so far," and you don't need anything fancier than that single number per distinct item, NSCountedSet reaches the answer with less code and less bookkeeping than a manual counting dictionary.

Using a plain NSSet to try to track how many times an item has occurred, then being surprised that the information is gone.

Use NSCountedSet from the start whenever the problem requires tracking occurrence counts, not just presence/absence.

Plain NSSet discards all information about repeated adds — the moment a duplicate add becomes a no-op, there is no way to recover how many times it was actually attempted. NSCountedSet exists specifically to retain that information.

Assuming [countedSet count] reports the total number of times objects were added (i.e. total occurrences across all elements).

Remember that count always reports the number of distinct elements, exactly as on any NSSet; use countForObject: for a specific object's occurrence tally.

count behaves identically on NSCountedSet as it does on NSSet or NSMutableSet — it is inherited, unchanged behavior. Only countForObject: exposes the per-object counting NSCountedSet adds.

Assuming removeObject: on an NSCountedSet deletes the object outright, the way it does on a plain NSMutableSet.

Expect removeObject: to decrement the object's tally by one; it only leaves the set entirely (countForObject: returning 0) once the tally reaches zero.

Because NSCountedSet tracks occurrences, a single removeObject: call only undoes one addObject: call — if an object was added three times, it takes three removeObject: calls to fully remove it.

What does NSCountedSet track that a plain NSSet does not? A. The order elements were added in B. How many times each distinct object was added (its occurrence count) C. The class name of each element D. Whether the set has ever been mutated

B. How many times each distinct object was added (its occurrence count) — NSCountedSet is a multiset (bag): it remembers, per distinct object, how many times addObject: has been called for it — information a plain NSSet discards when a duplicate add becomes a no-op.

Which method retrieves how many times a specific object has been added to an NSCountedSet? A. count B. containsObject: C. countForObject: D. occurrencesOfObject:

C. countForObject: — countForObject: takes an object and returns the number of times it has been added (net of removals); count, inherited from NSSet, always reports the number of distinct elements instead.

An object is added to an NSCountedSet three times. What do [set count] and [set countForObject:theObject] report, respectively? A. count returns 3, countForObject: returns 3 B. count returns 1, countForObject: returns 3 C. count returns 3, countForObject: returns 1 D. count returns 1, countForObject: returns 1

B. count returns 1, countForObject: returns 3 — count reports the number of distinct elements (1, since it's the same object each time), while countForObject: reports the actual occurrence tally (3) — these answer two different questions.

How does addObject: on NSCountedSet behave differently from addObject: on a plain NSMutableSet, when the object is already present? A. NSCountedSet raises an exception; NSMutableSet silently ignores it B. NSCountedSet increments the object's stored tally; NSMutableSet silently ignores the call entirely C. They behave identically in both cases D. NSCountedSet removes the object; NSMutableSet keeps it

B. NSCountedSet increments the object's stored tally; NSMutableSet silently ignores the call entirely — This is the defining contrast of the lesson: plain NSMutableSet treats a duplicate addObject: as a silent no-op, while NSCountedSet increments that object's occurrence tally each time.

Which problem is NSCountedSet specifically well suited to solving natively, without a hand-maintained dictionary of counts? A. Sorting a list of objects alphabetically B. Frequency counting, such as counting word occurrences in text or tallying votes C. Storing key-value pairs with arbitrary metadata D. Preserving strict insertion order for unique elements

B. Frequency counting, such as counting word occurrences in text or tallying votes — NSCountedSet is built exactly for frequency-counting problems — counting occurrences of discrete items — that would otherwise require manually maintaining an NSMutableDictionary mapping each item to an incrementing NSNumber count.

Given an NSArray of NSString log level tags (e.g. @"INFO", @"WARN", @"ERROR") pulled from a log file, use an NSCountedSet to count how many times each level occurred, then print the count for @"WARN" specifically using countForObject:.

NSArray *logLevels = @[@"INFO", @"WARN", @"ERROR", @"INFO", @"WARN", @"INFO"]; NSCountedSet *levelCounts = [NSCountedSet set]; for (NSString *level in logLevels) { [levelCounts addObject:level]; } NSLog(@"WARN occurred %lu times", (unsigned long)[levelCounts countForObject:@"WARN"]);

Loop over the array calling addObject: on an NSCountedSet for each tag, then call countForObject: once you're done.