NSOrderedSet: Uniqueness With Order

Understand NSOrderedSet as a hybrid collection combining NSSet's uniqueness guarantee with NSArray's stable, indexed ordering, and know when its tradeoffs make it the right choice over a plain array or set.

NSSet solved uniqueness but threw away order entirely; NSArray preserves order but allows unlimited duplicates. NSOrderedSet sits squarely between the two: it guarantees that every element is unique, exactly like NSSet, but it also remembers the exact order elements were inserted in and lets you access them by index, exactly like NSArray. This combination sounds like it should always be the better choice, but it comes with real performance tradeoffs that make it the right tool in some situations and overkill — or worse, a performance trap — in others. This lesson explains what NSOrderedSet actually is, where it shows up in real Objective-C codebases, and how it compares to the alternatives.

A hybrid collection: unique like a set, ordered like an array

NSOrderedSet guarantees two properties simultaneously that NSSet and NSArray each only provide one of. Like NSSet, no two elements in an NSOrderedSet can be equal to each other — uniqueness is enforced the same way, via isEqual: and hash, following the exact same contract you learned in the previous lesson. Like NSArray, an NSOrderedSet remembers a definite, stable sequence for its elements — the order you inserted them in (unless you explicitly reorder them) — and that sequence does not shuffle between enumerations the way a plain NSSet's can.

Because it has a defined order, NSOrderedSet also supports indexed access via objectAtIndex:, just like NSArray does. This is something plain NSSet cannot offer at all, since a set has no notion of "first" or "index 3" — asking for the object at a position is meaningless when the collection promises nothing about order. NSOrderedSet, by contrast, can answer objectAtIndex: just as directly as an array can, because internally it maintains both a hash table (for fast uniqueness checks) and an ordered index (for position-based access).

NSOrderedSet is the immutable base class; NSMutableOrderedSet is its mutable subclass. This mirrors every other collection pairing you've seen in Foundation — you create an immutable ordered set with class methods like orderedSetWithArray: or orderedSetWithObjects:, and reach for NSMutableOrderedSet when you need to insert, remove, or reorder elements after creation.

When order matters for a deduplicated collection

The clearest case for NSOrderedSet is when you need to deduplicate a sequence of items while preserving the order they were first encountered in. Imagine processing a stream of user actions and wanting a list of the distinct screens visited, in the order the user first visited each one — a plain NSSet would give you the right set of unique screens but would scramble the visit order; a plain NSArray would preserve order but let the same screen appear five times if the user revisited it. NSOrderedSet gives you exactly what the problem calls for: each screen appears exactly once, at the position corresponding to when it was first added, and adding it again later is simply ignored (just as it would be for NSSet).

NSOrderedSet is also the collection type Core Data (Apple's persistence framework, which you'll study in depth much later in this course) uses by default to represent an ordered to-many relationship — for example, the ordered list of pages belonging to a document, where each page must be a distinct managed object but the app also needs to remember which page comes first, second, and so on. You don't need to know anything about Core Data's mechanics yet; the point here is simply that NSOrderedSet is not just a theoretical Foundation curiosity — it's the natural backing collection whenever a real-world relationship needs both uniqueness and order at once.

Performance tradeoffs versus the alternatives

NSOrderedSet is not free — maintaining both a hash table for uniqueness and an ordered structure for positional access costs more in memory and often in raw speed than either NSArray or NSSet alone does for their single responsibility. If you only need order and are willing to write your own deduplication check (or simply don't care about duplicates), a plain NSArray, possibly paired with a helper NSSet used only during construction to filter duplicates, can be lighter weight than carrying an NSOrderedSet through your whole app.

Conversely, if you don't actually need to preserve insertion order — you just want unique elements and fast membership checks — reaching for NSOrderedSet instead of plain NSSet buys you nothing but extra overhead: you'd be paying for order-tracking machinery you never use. The rule of thumb is to let the actual requirement decide: need only uniqueness → NSSet; need only order (duplicates fine) → NSArray; need order *and* no duplicates *and* you plan to rely on that guarantee going forward → NSOrderedSet.

One practical middle ground worth knowing about: for a one-time deduplication where you don't need to keep enforcing uniqueness afterward, it's often simplest and fastest to build an NSMutableArray while checking membership against a scratch NSSet as you go (or, more simply, deduplicate with NSOrderedSet just at construction time and then convert to a plain NSArray with .array if you don't need ongoing uniqueness enforcement). NSOrderedSet itself actually exposes an array property and a set property that hand you a plain NSArray or NSSet view of its current contents, which is a convenient escape hatch when a later API specifically wants one of those simpler types.

Creating and querying NSOrderedSet

orderedSetWithArray: builds an NSOrderedSet from an existing array, keeping the first occurrence of each distinct element and discarding later duplicates while preserving the order of first appearance. orderedSetWithObjects: takes a nil-terminated list directly, exactly mirroring NSSet's and NSArray's convenience constructors.

Once built, you query an NSOrderedSet with a blend of NSArray-style and NSSet-style methods: objectAtIndex: for positional access, containsObject: for fast membership testing (still backed by the same hash mechanism as NSSet), indexOfObject: to find where a given element sits, and firstObject/lastObject for the ends of the sequence — all names that should feel immediately familiar from NSArray.

NSMutableOrderedSet: mutation methods

NSMutableOrderedSet adds the ability to change contents after creation. addObject: appends a new element at the end if — and only if — it isn't already present (an equal existing element makes the call a silent no-op, exactly as with NSMutableSet). insertObject:atIndex: lets you insert at a specific position, useful when order beyond simple append matters. removeObject: removes a given element by equality, and removeObjectAtIndex: removes whatever sits at a given position.

Because it also has a position-based structure, NSMutableOrderedSet additionally supports moveObjectsAtIndexes:toIndex: for reordering existing elements without removing and re-adding them, and exchangeObjectAtIndex:withObjectAtIndex: for swapping two positions — operations that have no equivalent at all on NSMutableSet, since plain sets have no positions to move things between.

Code Examples

The source array has six entries but only three distinct screens. orderedSetWithArray: keeps just the first occurrence of each (Home, then Profile, then Settings, in that order of first appearance) and gives you indexed access to that deduplicated sequence — something a plain NSSet could never offer since it has no notion of index or first appearance order. Expected result: Distinct screens: ( Home, Profile, Settings ) First screen visited: Home Screen at index 1: Profile

NSOrderedSet *distinctScreensInOrder = [NSOrderedSet orderedSetWithArray:screenVisits]; — duplicates collapse, but the order of first appearance survives.

This shows the mutation surface unique to NSMutableOrderedSet: appending, a no-op duplicate add, inserting at a specific index, and swapping two positions with exchangeObjectAtIndex:withObjectAtIndex: — an operation with no equivalent on a plain NSMutableSet, since sets don't have positions to exchange. Expected result: Playlist order: ( "Track Zero", "Track C", "Track B", "Track D" ) Contains Track C: YES

[playlist exchangeObjectAtIndex:1 withObjectAtIndex:2]; swaps two positions in place — a capability plain NSMutableSet cannot offer because it has no ordering.

NSArray *screenVisits = @[@"Home", @"Profile", @"Home", @"Settings", @"Profile", @"Home"];
NSOrderedSet *distinctScreensInOrder = [NSOrderedSet orderedSetWithArray:screenVisits];

NSLog(@"Distinct screens: %@", distinctScreensInOrder.array);
NSLog(@"First screen visited: %@", distinctScreensInOrder.firstObject);
NSLog(@"Screen at index 1: %@", [distinctScreensInOrder objectAtIndex:1]);
NSMutableOrderedSet *playlist = [NSMutableOrderedSet orderedSetWithObjects:@"Track A", @"Track B", @"Track C", nil];

[playlist addObject:@"Track D"];
[playlist addObject:@"Track B"]; // already present: silent no-op, order unchanged

[playlist insertObject:@"Track Zero" atIndex:0];
[playlist exchangeObjectAtIndex:1 withObjectAtIndex:2];

NSLog(@"Playlist order: %@", playlist.array);
NSLog(@"Contains Track C: %@", [playlist containsObject:@"Track C"] ? @"YES" : @"NO");

Where NSOrderedSet sits between NSArray and NSSet

NSOrderedSet takes NSArray's stable indexed order and NSSet's uniqueness guarantee and combines both in one collection.

NSArray:      ordered,  duplicates allowed     [A][B][A][C]
NSSet:        unordered, unique                {A, B, C}  (no fixed order)
NSOrderedSet: ordered,   unique                [A][B][C]  (index 0,1,2 stable)

Important Rules

NSOrderedSet guarantees both uniqueness (via isEqual:/hash, exactly like NSSet) and a stable, index-accessible order (exactly like NSArray).

objectAtIndex:, firstObject, and lastObject work on NSOrderedSet exactly as they do on NSArray — something plain NSSet cannot support.

NSOrderedSet costs more in memory/overhead than NSArray or NSSet alone; choose it only when you genuinely need both properties simultaneously.

Core Data uses NSOrderedSet (and NSMutableOrderedSet) as the default representation for an ordered to-many relationship, a topic covered later in this course.

The .array and .set properties give you a plain NSArray or NSSet view of an NSOrderedSet's current contents when a later API specifically requires one of those simpler types.

Ordered, deduplicated relationships

Beyond Core Data's ordered to-many relationships, NSOrderedSet is a natural fit for any feature that needs a "recently used, no repeats" list — recently viewed products where revisiting a product should move it (or leave it) at a known position rather than duplicating it, a deduplicated undo/redo-adjacent history of distinct states visited in order, or a playlist-like feature where each track can appear only once but the sequence the user built still matters. Anywhere the phrase "unique items, but the order I added them in matters" appears in a spec, NSOrderedSet is worth considering before reaching for a hand-rolled array-plus-set combination.

Reaching for NSOrderedSet by default whenever a collection needs to hold objects, without checking whether uniqueness is actually required.

Use NSArray when duplicates are fine and only order matters; reserve NSOrderedSet for when both uniqueness and order are real requirements.

NSOrderedSet carries the overhead of maintaining both a hash table and an ordered structure. Using it as a default "safe" collection wastes memory and cycles for cases where a plain array or set would have been simpler and cheaper.

Using NSOrderedSet purely for its uniqueness guarantee, when insertion order is never actually consulted anywhere in the code.

Use plain NSSet if order is never read; NSOrderedSet's ordering machinery is pure overhead if nothing ever calls objectAtIndex:, firstObject, or relies on enumeration order.

If your code never depends on the order of elements, paying for order-tracking is wasted cost — NSSet gives you the same uniqueness guarantee more cheaply.

Assuming addObject: on an NSMutableOrderedSet always appends and grows the collection, even for an object already present.

Remember that addObject: is a silent no-op when an equal object is already in the ordered set — the count and order stay unchanged, exactly as with NSMutableSet.

NSOrderedSet inherits NSSet's uniqueness enforcement; re-adding an existing element does not move it to the end or duplicate it, it simply does nothing.

What two guarantees does NSOrderedSet combine that NSSet and NSArray each only provide one of? A. Thread safety and mutability B. Uniqueness (like NSSet) and a stable, indexable order (like NSArray) C. Key-based lookup and sorted order D. Automatic memory management and fast enumeration

B. Uniqueness (like NSSet) and a stable, indexable order (like NSArray) — NSOrderedSet guarantees every element is unique, like NSSet, while also maintaining a stable insertion order with indexed access, like NSArray — a combination neither collection alone provides.

Which method can you call on an NSOrderedSet that you cannot call on a plain NSSet? A. containsObject: B. objectAtIndex: C. count D. isEqual:

B. objectAtIndex: — objectAtIndex: requires a defined position for each element, which plain NSSet does not have. NSOrderedSet supports it because it maintains a stable order alongside its uniqueness guarantee.

Why might using NSOrderedSet be worse than plain NSArray in a situation where duplicates are actually acceptable and order is the only real requirement? A. NSOrderedSet cannot be enumerated at all B. NSOrderedSet silently drops duplicate elements you actually wanted to keep, and its extra hash-table overhead is unnecessary C. NSOrderedSet does not support indexed access, so you'd need NSArray anyway D. NSOrderedSet cannot be created from an existing array

B. NSOrderedSet silently drops duplicate elements you actually wanted to keep, and its extra hash-table overhead is unnecessary — NSOrderedSet enforces uniqueness, so it would silently discard duplicates a plain NSArray would have kept, and it pays for hash-table overhead an array doesn't need — a poor fit when duplicates are actually meaningful.

According to this lesson, what Apple framework (covered later in this course) commonly uses NSOrderedSet by default? A. UIKit, for view hierarchies B. Core Data, for ordered to-many relationships C. Foundation's networking classes D. Grand Central Dispatch, for queue ordering

B. Core Data, for ordered to-many relationships — Core Data uses NSOrderedSet/NSMutableOrderedSet as the default representation of an ordered to-many relationship, where related objects must be both distinct and sequenced.

What happens when addObject: is called on an NSMutableOrderedSet with an element that isEqual: one already present? A. The element moves to the end of the order and stays unique B. The call is a silent no-op; the collection's contents and order remain unchanged C. An NSInvalidArgumentException is raised D. The ordered set becomes unordered as a fallback

B. The call is a silent no-op; the collection's contents and order remain unchanged — Just like NSMutableSet, NSMutableOrderedSet enforces uniqueness: adding an object equal to one already present does nothing — it does not move, duplicate, or reorder anything.

Given an NSArray of recently opened file paths (which may contain the same path more than once, since the user reopened some files), build an NSOrderedSet that preserves only the first time each path was opened, in that original order, then print the path at index 0 and confirm a specific path is present.

NSArray *recentFiles = @[@"main.m", @"utils.m", @"main.m", @"AppDelegate.m"]; NSOrderedSet *distinctFiles = [NSOrderedSet orderedSetWithArray:recentFiles]; NSLog(@"First opened: %@", [distinctFiles objectAtIndex:0]); NSLog(@"Contains utils.m: %@", [distinctFiles containsObject:@"utils.m"] ? @"YES" : @"NO");

orderedSetWithArray: does the deduplication-with-order-preservation for you in one call; objectAtIndex: and containsObject: handle the rest.