JavaScript
Setis an insertion-ordered collection of unique values that is most useful in interviews for duplicate detection, membership tests, sliding windows, and visited-state tracking.
What Is a JavaScript Set?
A Set is a collection in which each distinct ECMAScript value can appear at most once. It preserves the order in which values are first inserted. Adding an existing value again does not create a second entry or move the original entry.
const topics = new Set();
topics.add("arrays");
topics.add("sets");
topics.add("arrays");
console.log([...topics]);
[ 'arrays', 'sets' ]
The array contains "arrays" and "sets". The important result is that "arrays" occurs once.
Three roles make Set especially useful in interviews:
- Enforcing uniqueness, such as removing repeated primitive values or rejecting a duplicate identifier.
- Testing membership, such as asking whether the current character already belongs to a sliding window.
- Tracking visited state, such as preventing a graph traversal from processing the same node twice.
The recognition rule is practical. Choose a Set when the algorithm asks, "Have I seen this value?" and does not also need a count or associated data. Choose a Map when the question becomes, "How many times have I seen it?" or "What information belongs to it?"
A Set is iterable, so it works naturally with for...of and array conversion. It is not indexed. values[0] has meaning for an array, but a Set has no equivalent indexed lookup.
Creating and Using a Set
The Set constructor accepts an iterable. An iterable is a value whose elements JavaScript can visit in sequence, such as an array or string.
const languages = new Set(["JavaScript", "TypeScript", "JavaScript"]);
console.log(languages.size);
console.log(languages.has("TypeScript"));
console.log([...languages]);
2
true
[ 'JavaScript', 'TypeScript' ]
Spread syntax converts the Set back into an array. This makes [...new Set(values)] a compact way to remove duplicate primitive values while retaining first-insertion order.
The core API has one property and four commonly tested methods:
sizereports the number of values.add(value)inserts a value and returns the sameSet, so calls can be chained.has(value)reports whether the value is present.delete(value)removes a value and reports whether an entry was removed.clear()removes all values.
const levels = new Set();
levels.add("easy").add("medium").add("hard");
console.log(levels.size);
console.log(levels.delete("medium"));
console.log(levels.delete("missing"));
levels.clear();
console.log(levels.size);
3
true
false
0
A Set provides several iteration forms. values() yields its values. keys() is the same function as values() for compatibility with collection interfaces. entries() yields [value, value] pairs. forEach() receives the value twice in its first two callback parameters.
const scores = new Set([10, 20]);
for (const score of scores) {
console.log(`for:${score}`);
}
console.log([...scores.values()]);
console.log([...scores.keys()]);
console.log([...scores.entries()]);
scores.forEach((value, repeatedValue) => {
console.log(`${value}:${repeatedValue}`);
});
for:10
for:20
[ 10, 20 ]
[ 10, 20 ]
[ [ 10, 10 ], [ 20, 20 ] ]
10:10
20:20
This API is a common companion to JavaScript array questions. The interview decision is usually more important than recalling every method: determine whether the problem needs order, indexes, counts, or unique membership.
How Set Decides Whether Values Are Equal
Set uses the SameValueZero comparison rule. For most primitive values, that behaves like the equality expected during duplicate removal. It also treats NaN as equal to itself and treats +0 and -0 as the same value.
const values = new Set([
"x",
"x",
NaN,
NaN,
+0,
-0,
true,
true,
]);
console.log(values.size);
console.log(values.has(NaN));
console.log(values.has(-0));
4
true
true
The four stored values are "x", NaN, 0, and true.
Objects follow identity rather than field-by-field comparison. Two variables match when they refer to the same object. Two separately allocated objects remain distinct even if their properties look identical.
const first = { id: 7 };
const alias = first;
const separate = { id: 7 };
const records = new Set([first, alias, separate]);
console.log(records.size);
console.log(records.has(first));
console.log(records.has({ id: 7 }));
2
true
false
This rule explains why new Set(arrayOfObjects) does not deduplicate records by id. It removes repeated references only.
For key-based object deduplication, track the chosen key explicitly:
const candidates = [
{ id: 7, name: "Ari" },
{ id: 7, name: "Ari again" },
{ id: 9, name: "Bo" },
];
const seenIds = new Set();
const uniqueCandidates = candidates.filter((candidate) => {
if (seenIds.has(candidate.id)) return false;
seenIds.add(candidate.id);
return true;
});
console.log(uniqueCandidates.map((candidate) => candidate.name));
[ 'Ari', 'Bo' ]
A Map is another option when each key should retain a full record. That distinction often appears beside JavaScript type utility exercises because the runtime collection and the static type solve different parts of the problem.
Set Operations and Choosing the Right Collection
JavaScript includes methods for mathematical set operations. These methods return new Set objects rather than changing the receiver.
const frontend = new Set(["JavaScript", "CSS", "HTML"]);
const typed = new Set(["JavaScript", "TypeScript"]);
console.log([...frontend.union(typed)]);
console.log([...frontend.intersection(typed)]);
console.log([...frontend.difference(typed)]);
console.log([...frontend.symmetricDifference(typed)]);
console.log(new Set(["JavaScript"]).isSubsetOf(frontend));
console.log(frontend.isSupersetOf(new Set(["CSS"])));
console.log(frontend.isDisjointFrom(new Set(["Rust"])));
[ 'JavaScript', 'CSS', 'HTML', 'TypeScript' ]
[ 'JavaScript' ]
[ 'CSS', 'HTML' ]
[ 'CSS', 'HTML', 'TypeScript' ]
true
true
true
union() contains values from either set. intersection() keeps values shared by both. difference() keeps values from the receiver that the argument lacks. symmetricDifference() keeps values found in one set but not both.
The three predicate methods answer relationship questions. isSubsetOf() checks whether every receiver value occurs in the argument. isSupersetOf() checks the reverse relationship. isDisjointFrom() checks whether the collections share no values.
These composition methods are modern APIs. Verify the runtime used by the interview platform or project before relying on them. A loop provides an explicit alternative:
function intersection(left, right) {
const result = new Set();
for (const value of left) {
if (right.has(value)) result.add(value);
}
return result;
}
console.log([...intersection(new Set([1, 2, 3]), new Set([2, 4]))]);
[ 2 ]
Use the collection whose operations match the problem:
| Collection | Best fit | Membership or lookup key | Important limitation |
|---|---|---|---|
Set | Unique values and visited state | A stored value | No indexes or occurrence counts |
Array | Ordered sequences with positional access | Numeric index | Membership checks may require scanning |
Map | Keys associated with counts or data | Any supported key value | More information than a membership-only problem needs |
Object | Named properties in an object-shaped record | Property key | Its semantics differ from a dedicated collection |
WeakSet | Identity membership for eligible values without enumeration | Stored object identity | It is not iterable and is not a drop-in replacement for Set |
Use an array when position matters. Use a Set when uniqueness or membership drives the algorithm. Use a Map when the value needs a count, index, predecessor, or other attached data. This choice also appears in coding interview examples where changing one requirement changes the right data structure.
Three Set Patterns for JavaScript Interviews
Duplicate detection uses a Set of values already seen
The invariant is: before processing index i, seen contains exactly the distinct values in values[0] through values[i - 1].
function containsDuplicate(values) {
const seen = new Set();
for (const value of values) {
if (seen.has(value)) return true;
seen.add(value);
}
return false;
}
console.log(containsDuplicate([4, 2, 7, 2]));
console.log(containsDuplicate([4, 2, 7, 9]));
true
false
For an input of length n, the loop performs at most n membership checks and additions. Under the common assumption that these operations take constant average time, the algorithm takes O(n) time and O(n) extra space. State that assumption. ECMAScript requires sublinear average access, not universal O(1) operations.
If the follow-up asks for occurrence counts, replace the Set with a frequency Map. Membership alone no longer represents the required state.
Another follow-up might ask: “The values are integers from 0 through 255; can you avoid input-dependent extra space?” The bounded domain permits a fixed-size lookup table:
function containsDuplicateByte(values) {
const seen = new Uint8Array(256);
for (const value of values) {
if (seen[value] === 1) return true;
seen[value] = 1;
}
return false;
}
Before each iteration, seen[value] is 1 exactly when that value has appeared in the processed prefix. Under the usual constant-time indexed-access model, this still takes O(n) time, but its 256-byte table is O(1) extra space because the domain is fixed; the Set version can use O(n) extra space.
A sliding window Set tracks the current substring
For the longest substring without repeated Unicode code points, the invariant is: window contains exactly the code points from left through right, and every code point inside that range is unique.
function longestUniqueSubstring(text) {
const codePoints = [...text];
const window = new Set();
let left = 0;
let best = 0;
for (let right = 0; right < codePoints.length; right += 1) {
while (window.has(codePoints[right])) {
window.delete(codePoints[left]);
left += 1;
}
window.add(codePoints[right]);
best = Math.max(best, right - left + 1);
}
return best;
}
console.log(longestUniqueSubstring("abba"));
console.log(longestUniqueSubstring("frontend"));
2
6
Trace "abba" one character at a time:
right | Character | Action | Window after action | best |
|---|---|---|---|---|
| 0 | a | Add a | a | 1 |
| 1 | b | Add b | a, b | 2 |
| 2 | b | Delete from the left until b is absent, then add b | b | 2 |
| 3 | a | Add a | b, a | 2 |
Each Unicode code point enters the window once and leaves at most once. With constant-average-time Set operations as an explicit assumption, the complete algorithm takes O(n) time and up to O(n) extra space. This pattern is a useful extension of JavaScript coding interview practice.
Graph traversal uses a Set of visited nodes
The invariant is: every value in visited has been discovered, and no discovered node is added to the work list twice.
function reachableValues(graph, start) {
const visited = new Set([start]);
const stack = [start];
const order = [];
while (stack.length > 0) {
const node = stack.pop();
order.push(node);
for (const neighbor of graph.get(node) ?? []) {
if (!visited.has(neighbor)) {
visited.add(neighbor);
stack.push(neighbor);
}
}
}
return order;
}
const graph = new Map([
["A", ["B", "C"]],
["B", ["C"]],
["C", ["A"]],
]);
console.log(reachableValues(graph, "A"));
[ 'A', 'C', 'B' ]
For V reachable vertices and E examined edges, this traversal takes O(V + E) time under constant-average-time assumptions for Set operations and Map.get(). The visited set uses O(V) extra space. The pattern also applies to DOM-like trees or graphs when object identity identifies each node.
Common Set Mistakes Interviewers Look For
The first mistake is claiming that Set.has() is guaranteed O(1). The specification requires average access to be sublinear. An interview solution may use a constant-average-time model, but the explanation should identify it as an analysis assumption.
The second mistake is expecting structural object deduplication:
const attempts = new Set([
{ questionId: 3 },
{ questionId: 3 },
]);
console.log(attempts.size);
2
Track questionId values in a separate Set, or use a Map keyed by questionId, when that field defines uniqueness.
The third mistake is using a Set when counts matter. A repeated value may require a frequency greater than one, while Set can record only presence. A Map fits that requirement.
The fourth mistake is trying to read set[0]. Insertion order controls iteration, but it does not turn Set into an indexed collection. Convert to an array if indexed access is truly required, then account for that conversion in the solution.
The fifth mistake is assuming every target runtime has the composition methods. Verify the interview runner or provide a loop-based implementation. Do not discover the mismatch after submitting otherwise correct logic.
The React mistake is more subtle. Mutating a Set stored in state preserves its object reference. React may skip a state update when the next value is identical to the current one according to Object.is.
function addSelectedId(setSelectedIds, id) {
setSelectedIds((previous) => {
const next = new Set(previous);
next.add(id);
return next;
});
}
function removeSelectedId(setSelectedIds, id) {
setSelectedIds((previous) => {
const next = new Set(previous);
next.delete(id);
return next;
});
}
Each updater creates a new Set, applies the change, and returns the new reference. This is the safer pattern for React interview questions.
For longer practice sessions with the browser editor and worked solutions, UIReady Premium Lifetime can provide additional interview exercises.
JavaScript Set Interview Checklist
Before submitting a Set solution, verify the following points:
- The problem needs unique membership or visited-state tracking, not counts or attached data.
- The equality rule matches the input. Primitive values use
SameValueZero, while objects use identity. - Object uniqueness is based on references, or the solution tracks an explicit field such as
id. - The algorithm does not require numeric indexing from the
Set. - The invariant states exactly what the
Setcontains at each step. - Complexity describes the complete algorithm, including loops, deletions, conversions, and other collections.
- Any constant-time membership claim is presented as an analysis assumption, not an ECMAScript guarantee.
- Composition methods have been checked against the target runtime, or the solution includes an explicit loop.
- A React state update returns a new
Setreference. - Likely follow-ups have answers: use a
Mapfor frequencies, use a constrained lookup structure when the value domain permits one, and use an array when positional access is required.