Basic Data Structures (Objects, Maps, Sets) Questions
Understand how objects work in JavaScript including prototypal inheritance and property descriptors. Know when to use Maps vs Objects and Sets vs Arrays. Understand the performance characteristics of different data structures. Be comfortable with nested data structures and how to manipulate them efficiently.
EasyTechnical
82 practiced
Describe the differences between JavaScript Set and Array. Explain membership test performance, how to use Set for deduplication while preserving order, how to convert between them, and trade-offs when choosing an Array vs a Set in token pipelines.
Sample Answer
Definition and key differences:- Array: ordered, indexable collection allowing duplicates. Good for ordered iteration, random access by index, and operations like map/filter/reduce.- Set: ES6 collection of unique values (based on SameValueZero equality), preserves insertion order but has no numeric indices. Ideal when uniqueness is primary.Membership test performance:- Array: indexOf/includes is O(n) average — scans items.- Set: has(value) is on average O(1) (hash-backed semantics in engines). For many membership checks (e.g., filtering tokens against a stoplist), Set is significantly faster.Deduplication while preserving order:- Sets preserve insertion order, but constructing a Set from an Array removes duplicates and keeps the first occurrence order. Example:If you need to keep last occurrence instead, iterate in reverse:Converting between Array and Set:- Array -> Set: new Set(array)- Set -> Array: Array.from(set) or [...set]Trade-offs for token pipelines (AI Engineer context):- Use Set when: - You need fast membership tests (stopwords, seen-token deduplication) at scale. - Uniqueness is required and order is either insertion-based or irrelevant. - Memory cost of deduplicated set is acceptable.- Use Array when: - Token order, positional indices, or duplicates matter (e.g., language models relying on token positions, n-gram counts). - You need array methods (map/filter) that implicitly rely on indices or stable iteration patterns.- Practical hybrid patterns: - Keep tokens as Array for modeling but use a Set for auxiliary operations (e.g., seen = new Set(); filter tokens by checking seen.has(token) to remove duplicates while building an array). - For large vocab/token streams, prefer Sets for seen-checks to avoid O(n^2) behavior.Example in a pipeline removing duplicate tokens while preserving first order:Summary: Arrays for ordered sequences and model inputs; Sets for fast uniqueness and membership checks. Combine both to get best performance and semantics in token pipelines.
javascript
// preserve first occurrence order
const arr = ['a','b','a','c','b'];
const deduped = Array.from(new Set(arr)); // ['a','b','c']javascript
const arr = ['a','b','a','c','b'];
const seen = new Set();
const reversedKeepLast = [];
for (let i = arr.length - 1; i >= 0; i--) {
if (!seen.has(arr[i])) {
seen.add(arr[i]);
reversedKeepLast.push(arr[i]);
}
}
const result = reversedKeepLast.reverse(); // keeps last occurrences in original orderjavascript
function dedupeTokens(tokens) {
const seen = new Set();
const out = [];
for (const t of tokens) {
if (!seen.has(t)) {
seen.add(t);
out.push(t);
}
}
return out;
}MediumTechnical
69 practiced
Explain how JavaScript's prototype chain influences property lookup performance and describe how modern JIT engines such as V8 use hidden classes and inline caches to accelerate property access. For an AI Engineer, what data-layout patterns reduce cache misses and improve throughput in hot preprocessing loops?
Sample Answer
Prototype chain affects property lookup cost because JS engines must walk an object's shape (its own properties then prototypes) until a property is found. Deep or dynamic prototype changes force repeated lookups and prevent the engine from optimizing access — more pointer chasing and cache misses.Modern JITs (like V8) avoid repeated prototype walks by creating hidden classes (shapes): lightweight descriptors representing an object’s layout when properties are added in a consistent order. When shapes are stable, the engine generates direct offsets into object memory. Inline caches (ICs) record the observed shape(s) at a call site: monomorphic ICs (single shape) let the JIT emit a fast direct load; polymorphic ICs can handle a few shapes; megamorphic sites fall back to slower generic lookup.For an AI Engineer optimizing hot preprocessing loops:- Keep object shapes stable (add all fields at construction) to produce monomorphic ICs.- Prefer flat data layouts: use arrays of primitives (typed arrays, Float32Array) instead of arrays of objects (AoS → use SoA when possible).- Use contiguous memory and avoid pointer-chasing: pack features into buffers for SIMD/GPU, align to cache lines, and batch-process vectors.- Minimize temporary allocations (reuse buffers, memory pools) to reduce GC and cache churn.- Consider WebAssembly or native modules for very hot kernels to get predictable, contiguous layouts and lower overhead.These patterns reduce cache misses and let the JIT emit fast, inlined property/field access, improving throughput in preprocessing hotspots.
HardTechnical
77 practiced
Implement a JavaScript class StructuralMap that works like Map but allows arrays or plain objects as keys by using structural (deep) equality for key comparison rather than reference equality. Provide an implementation approach using canonicalization or hashing, address collision handling, cycles, and discuss performance trade-offs.
Sample Answer
Approach overview:- Canonicalize keys to a stable string/hash that is identical for structurally equal objects/arrays. Use a deterministic serializer that handles property ordering and detects cycles with a WeakMap to assign stable IDs. Use that canonical string as the primary map key in an internal Map. To handle collisions (two different structures producing same canonical string or intentional hash shortening), store a bucket array for each canonical key and compare with a deepEqual fallback.Code (ES2020+):Key points and reasoning:- Canonicalization produces stable representations: property keys are sorted, arrays are ordered, cycles are represented by reference IDs so recursive structures don't blow up.- Collisions: even with deterministic strings, collisions can occur (or if you switch to shorter hashes). We therefore store buckets and resolve using a correct deep equality fallback—this guarantees correctness.- Cycle handling: WeakMap tracks seen objects; canonicalizer emits references (@ref:id) so cyclic graphs canonicalize deterministically.- Performance trade-offs: - Pros: Correct structural equality for complex keys; works for nested objects and cycles. - Cons: Canonicalization and deepEqual are O(n) in key size; creating strings and recursion add allocations and GC pressure. For many small keys or performance-critical hot paths, this is slower than native Map (O(1) reference). - Improvements: Cache canonical strings for object identities (WeakMap key -> canonical) to avoid re-serializing frequently used keys; use a fast non-cryptographic hash (e.g., xxhash) to shorten stored keys and reduce memory, but still keep bucket+deepEqual to be safe.- Alternatives: - Use structural hashing at insertion time and store a fingerprint plus bucket. - Use immutable.js structural sharing that provides cheap structural equality via interned nodes.- Practical recommendation: For moderate-sized keys and where semantic equality matters (caching model inputs, memoization in AI pipelines), this approach is appropriate. For high-throughput paths, benchmark and add caching or use a reference-stable wrapper (interning).
javascript
class StructuralMap {
constructor() {
// primary map: canonicalString -> [{ key, value }]
this._map = new Map();
}
// deterministic canonicalizer with cycle handling
static _canonicalize(obj) {
const seen = new WeakMap();
let nextId = 0;
function serialize(x) {
if (x === null) return 'null';
if (x === undefined) return 'undefined';
if (typeof x === 'number' || typeof x === 'boolean') return JSON.stringify(x);
if (typeof x === 'string') return JSON.stringify(x);
if (typeof x === 'symbol') return `@symbol:${String(x)}`;
if (typeof x === 'function') return `@function:${(x.name || '<anon>')}`;
if (seen.has(x)) return `@ref:${seen.get(x)}`; // cycle reference
const id = nextId++;
seen.set(x, id);
if (Array.isArray(x)) {
const parts = x.map(serialize).join(',');
return `@arr:${id}:[${parts}]`;
}
// plain object: sort keys to ensure canonical order
const keys = Object.keys(x).sort();
const parts = keys.map(k => `${JSON.stringify(k)}:${serialize(x[k])}`).join(',');
return `@obj:${id}:{${parts}}`;
}
return serialize(obj);
}
// shallow deep equality for collision resolution
static _deepEqual(a, b, aSeen = new WeakMap(), bSeen = new WeakMap()) {
if (a === b) return true;
if (a === null || b === null) return a === b;
if (typeof a !== typeof b) return false;
if (typeof a !== 'object') return a === b;
if (aSeen.has(a) || bSeen.has(b)) return aSeen.get(a) === bSeen.get(b);
const id = aSeen.size;
aSeen.set(a, id);
bSeen.set(b, id);
if (Array.isArray(a)) {
if (!Array.isArray(b) || a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) if (!this._deepEqual(a[i], b[i], aSeen, bSeen)) return false;
return true;
}
const ka = Object.keys(a).sort();
const kb = Object.keys(b).sort();
if (ka.length !== kb.length) return false;
for (let i = 0; i < ka.length; i++) if (ka[i] !== kb[i]) return false;
for (const k of ka) if (!this._deepEqual(a[k], b[k], aSeen, bSeen)) return false;
return true;
}
set(key, value) {
const canon = StructuralMap._canonicalize(key);
const bucket = this._map.get(canon) || [];
// try to find exact structural match in bucket
for (const entry of bucket) {
if (StructuralMap._deepEqual(entry.key, key)) {
entry.value = value;
this._map.set(canon, bucket);
return this;
}
}
bucket.push({ key, value });
this._map.set(canon, bucket);
return this;
}
get(key) {
const canon = StructuralMap._canonicalize(key);
const bucket = this._map.get(canon);
if (!bucket) return undefined;
for (const entry of bucket) if (StructuralMap._deepEqual(entry.key, key)) return entry.value;
return undefined;
}
has(key) {
const canon = StructuralMap._canonicalize(key);
const bucket = this._map.get(canon);
if (!bucket) return false;
for (const entry of bucket) if (StructuralMap._deepEqual(entry.key, key)) return true;
return false;
}
delete(key) {
const canon = StructuralMap._canonicalize(key);
const bucket = this._map.get(canon);
if (!bucket) return false;
for (let i = 0; i < bucket.length; i++) {
if (StructuralMap._deepEqual(bucket[i].key, key)) {
bucket.splice(i, 1);
if (bucket.length === 0) this._map.delete(canon);
else this._map.set(canon, bucket);
return true;
}
}
return false;
}
// optional size and iteration helpers
*entries() {
for (const bucket of this._map.values()) for (const e of bucket) yield [e.key, e.value];
}
[Symbol.iterator]() { return this.entries(); }
}EasyTechnical
68 practiced
Write a JavaScript function removeDuplicates(arr) that takes an array of strings and returns a new array with duplicates removed while preserving original order. Use ES6 syntax, explain time and space complexity, and include a short usage example.
Sample Answer
A simple and efficient way is to iterate once through the array, track seen strings with a Set, and push only the first occurrence into a result array. This preserves original order and is O(n) time on average.Key points:- Uses ES6 Set for O(1) average membership checks.- Preserves first-seen order by pushing into result only on first encounter.Time complexity: O(n) average, where n = arr.length.Space complexity: O(n) additional (Set + result) in the worst case.Edge cases:- null/undefined input handled by default parameter arr = [].- Works with empty arrays, duplicate-only arrays, and strings with different cases (case-sensitive).
javascript
// removeDuplicates.js
const removeDuplicates = (arr = []) => {
const seen = new Set();
const result = [];
for (const s of arr) {
if (!seen.has(s)) {
seen.add(s);
result.push(s);
}
}
return result;
};
// Usage example
console.log(removeDuplicates(['apple', 'banana', 'apple', 'orange', 'banana']));
// Output: ['apple', 'banana', 'orange']HardTechnical
77 practiced
A third-party library returns deeply nested JSON from untrusted sources which you later merge into application state. Walk through a secure sanitation pipeline in JavaScript that prevents prototype pollution, filters dangerous keys, normalizes types, and reconstructs data into safe null-prototype maps or validated structures suitable for downstream AI processing.
Sample Answer
Situation: We receive deeply nested JSON from an untrusted third-party library and must safely merge it into application state used by downstream AI pipelines.Pipeline overview (steps):1. Reject non-object top-level types2. Deep sanitize traversal that: - forbids prototype keys (__proto__, constructor, prototype) - skips inherited properties - filters dangerous keys (e.g., eval, Function) by allowlist/denylist - normalizes primitives (strings, numbers, booleans), coerces known shapes, rejects surprises - limits depth/size to prevent DoS3. Reconstruct objects into null-prototype maps or strict validated classes4. Return validated DTOs or throwExample implementation highlights:Validation/reconstruction:- After sanitize, run JSON Schema or Zod validation to coerce and enforce required fields, ranges, tokenization-friendly formats. Example: use Zod to parse sanitized null-prototype maps into typed DTOs for AI inputs.- Example: const input = MySchema.parse(sanitized); // throws on mismatchReasoning and trade-offs:- Using Object.keys and Object.create(null) avoids inherited props and prototype injection.- Denylist dangerous keys plus an allowlist at schema stage gives layered defense.- Depth/size limits and rejecting non-serializable types mitigate DoS and model poisoning risks.- Final schema validation ensures downstream AI receives normalized, safe types.Edge cases:- If trusted nested HTML/markdown must be preserved, run an HTML sanitizer (DOMPurify) on string fields only.- Log but do not expose original payload on validation failure.
javascript
const DANGEROUS_KEYS = new Set(['__proto__','constructor','prototype','eval','Function']);
const MAX_DEPTH = 10;
const MAX_NODES = 1e6;
function isPlainObject(x){ return x !== null && typeof x === 'object' && Object.getPrototypeOf(x) === Object.prototype; }
function sanitize(value, depth=0, counter={n:0}){
if (++counter.n > MAX_NODES) throw new Error('payload too large');
if (depth > MAX_DEPTH) throw new Error('depth limit');
if (Array.isArray(value)){
return value.map(v => sanitize(v, depth+1, counter));
}
if (value && typeof value === 'object'){
const out = Object.create(null); // null-prototype safe map
for (const k of Object.keys(value)){ // only own enumerable keys
if (DANGEROUS_KEYS.has(k)) continue; // drop dangerous keys
const v = value[k];
// type normalization examples:
if (v === '') { out[k] = null; continue; }
if (typeof v === 'number' || typeof v === 'boolean' || typeof v === 'string'){
out[k] = v;
continue;
}
if (typeof v === 'object') out[k] = sanitize(v, depth+1, counter);
// ignore functions, symbols, undefined
}
return out;
}
// primitives
if (['string','number','boolean'].includes(typeof value)) return value;
return null;
}Unlock Full Question Bank
Get access to hundreds of Basic Data Structures (Objects, Maps, Sets) interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.