Python · TypeScript · C# toolkit
In an interview you shouldn’t have to think about which call gives you a heap or how division rounds. This page puts the interview-relevant standard library of the three languages side by side, and flags the traps (⚠) that quietly produce wrong answers.
Baseline versions: Python 3.12+, TypeScript on Node 22 (ES2024 library), C# 12 on .NET 8+. Anything newer is marked (Python 3.14 heapq max-heap functions, .NET 9 CountBy and Index). Every snippet below runs and checks itself.
The table
Section titled “The table”Filter by area, search for a word (“round”, “tuple”, “stable”), hide languages you don’t use, or show only the traps.
[0] * n
new Array<number>(n).fill(0)
new int[n] // zero-filled new List<int>(capacity: n) // empty
[[0] * c for _ in range(r)]
Array.from({ length: r }, () =>
new Array<number>(c).fill(0))new int[r, c] // rectangular new int[r][] // jagged, fill rows
xs.append(x) xs.pop()
xs.push(x) xs.pop()
list.Add(x) list.RemoveAt(list.Count - 1)
xs[-1]
xs.at(-1) // or xs[xs.length - 1]
xs[^1]
xs[i:j]
xs.slice(i, j)
arr[i..j] // arrays: a copy list.GetRange(i, j - i)
xs.insert(i, x) del xs[i]
xs.splice(i, 0, x) xs.splice(i, 1)
list.Insert(i, x) list.RemoveAt(i)
x in xs xs.index(x) # ValueError if absent
xs.includes(x) xs.indexOf(x) // -1 if absent
xs.Contains(x) list.IndexOf(x) // -1 if absent
xs.reverse() # in place xs[::-1] # copy
xs.reverse() // in place xs.toReversed() // copy (ES2023)
Array.Reverse(arr) // in place list.Reverse() // in place
xs.copy() # shallow copy.deepcopy(xs)
[...xs] // shallow structuredClone(xs)
[.. xs] // shallow (int[])arr.Clone()
d: dict[str, int] = {}const m = new Map<string, number>()
var d = new Dictionary<string, int>();
d.get(k, 0)
m.get(k) ?? 0
d.GetValueOrDefault(k) d.TryGetValue(k, out var v)
d[k] # KeyError
m.get(k) // undefined
d[k] // KeyNotFoundException
d[k] = v k in d d.pop(k, None)
m.set(k, v) m.has(k) m.delete(k)
d[k] = v // Add(k, v) throws if present d.ContainsKey(k) d.Remove(k)
for k, v in d.items():
for (const [k, v] of m) {}foreach (var (k, v) in d) {}insertion order (guaranteed since 3.7)
insertion order (guaranteed)
unspecified: do not rely on it SortedDictionary for key order
len(d)
m.size
d.Count
—
avoid {}: keys become strings,
and "constructor" etc. already exist—
s = set() s.add(x); s.discard(x) x in s
const s = new Set<number>() s.add(x); s.delete(x) s.has(x)
var s = new HashSet<int>(); s.Add(x); s.Remove(x); s.Contains(x)
a | b, a & b, a - b
a.union(b), a.intersection(b), a.difference(b) // Node 22+
a.UnionWith(b) // mutates a! a.IntersectWith(b); a.ExceptWith(b) a.Union(b) // LINQ, new sequence
q = deque() q.append(x); q.popleft()
no queue: use a head index (q.shift() is O(n))
var q = new Queue<int>(); q.Enqueue(x); q.Dequeue()
q.appendleft(x); q.pop() deque(maxlen=k) # bounded
none: ring buffer or two stacks
none: LinkedList<T> with AddFirst / RemoveLast
st.append(x); st.pop(); st[-1]
st.push(x); st.pop(); st.at(-1)
var st = new Stack<int>(); st.Push(x); st.Pop(); st.Peek()
heapq.heappush(h, x) heapq.heappop(h) h[0]
none built in: write a ~30-line binary heap
var pq = new PriorityQueue<string, int>(); pq.Enqueue(item, prio); pq.Dequeue() pq.Peek()
heapq.heapify(xs) # in place
(your heap)
new PriorityQueue<E, P>(pairs)
push -x, negate on pop # 3.14+: heapq.heappush_max(h, x)
flip the comparator
new PriorityQueue<int, int>( Comparer<int>.Create((a, b) => b.CompareTo(a)))
push (prio, seq, item)
compare (prio, seq)
priority (prio, seq) as a value tuple
heapq.nlargest(k, xs, key=f)
sort + slice, or a size-k heap
xs.OrderByDescending(f).Take(k)
no decrease-key: push again, skip stale entries on pop
same lazy-deletion trick
same trick; .NET 9 Remove() is a linear scan
none in stdlib (sortedcontainers is third-party: ask before using)
none: sorted array + binary search
SortedSet<T>, SortedDictionary<K, V> // red-black trees, O(log n)
bisect.bisect_left(xs, x) bisect.bisect_right(xs, x)
write lowerBound(xs, x)
Array.BinarySearch(arr, x) // returns ~insertionPoint if absent
bisect.insort(xs, x) # O(n) shift
xs.splice(lowerBound(xs, x), 0, x)
set.Add(x) // O(log n)
i = bisect_right(xs, x) - 1 # i == -1 means none
upperBound(xs, x) - 1
set.GetViewBetween(int.MinValue, x).Max // 0 (default) if the view is empty
xs[0], xs[-1]
xs[0], xs.at(-1)
set.Min, set.Max
xs.sort() sorted(xs) # copy
xs.sort((a, b) => a - b) xs.toSorted((a, b) => a - b)
Array.Sort(arr); list.Sort(); xs.Order() // LINQ, .NET 7+
yes (Timsort)
yes (required since ES2019)
Array.Sort / List.Sort: NO (introsort) LINQ OrderBy: yes
sorted(ps, key=lambda p: p.age)
ps.sort((a, b) => a.age - b.age)
ps.OrderBy(p => p.Age) list.Sort((a, b) => a.Age.CompareTo(b.Age))
key=lambda p: (-p.score, p.name)
(a, b) => b.score - a.score || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)
ps.OrderByDescending(p => p.Score) .ThenBy(p => p.Name, StringComparer.Ordinal)
sorted(xs, key=functools.cmp_to_key(cmp))
xs.sort(cmp)
list.Sort(cmp) Comparer<T>.Create(cmp)
a < b # code points
a < b // UTF-16 units localeCompare: locale-aware
string.CompareOrdinal(a, b) // default compare may be culture-aware
n/a (key functions)
a - b fine for normal numbers
a - b can overflow int: use a.CompareTo(b)
"".join(parts)
parts.join("") // += is also OK in V8var sb = new StringBuilder(); sb.Append(x); sb.ToString()
ord(c), chr(n)
s.charCodeAt(i), s.codePointAt(i) String.fromCharCode(n)
(int)c, (char)n c - 'a' // letter index
len(s) == 1 # code points
s.length === 2 // UTF-16 units [...s].length === 1
s.Length == 2 // UTF-16 units new StringInfo(s).LengthInTextElements
cs = list(s); "".join(cs)
const cs = [...s]; cs.join("")var cs = s.ToCharArray(); new string(cs)
s.split() # drops empties
s.split(" ") # keeps thems.trim().split(/\s+/)
s.Split(' ', StringSplitOptions.RemoveEmptyEntries)c.isdigit(), c.isalpha(), c.lower()
/[0-9]/.test(c), c.toLowerCase()
char.IsDigit(c), char.IsLetter(c) char.ToLowerInvariant(c)
s[::-1]
[...s].reverse().join("")string.Concat(s.Reverse())
unbounded int
Number.MAX_SAFE_INTEGER (2⁵³−1) bigint for more: 10n ** 30n
int.MaxValue (2³¹−1), long.MaxValue wraps silently unless checked
-7 // 2 == -4 # floor -7 % 2 == 1
Math.trunc(-7 / 2) === -3 -7 % 2 === -1
-7 / 2 == -3 // truncates -7 % 2 == -1
math.inf
Infinity
int.MaxValue (careful: +1 wraps) double.PositiveInfinity
round(2.5) == 2 # half to even
Math.round(2.5) === 3 Math.round(-2.5) === -2
Math.Round(2.5) == 2 // to even MidpointRounding.AwayFromZero
Decimal("0.1"), Fraction(1, 3)integer cents, or bigint
decimal: 0.1m + 0.2m == 0.3m
int("42") # ValueError if badNumber("42") // NaN if bad
parseInt("42px") === 42 (!)int.Parse("42") // throws
int.TryParse(s, out var n)x.bit_count(), x >> 1, x & 1
x >> 1, x & 1 // 32-bit only! x >>> 0 for unsigned
int.PopCount(x) // .NET 7+ x >> 1, x & 1
Counter(xs) cnt.most_common(k)
m.set(x, (m.get(x) ?? 0) + 1)
d[x] = d.GetValueOrDefault(x) + 1; xs.CountBy(x => x) // .NET 9+
g = defaultdict(list) g[k].append(x)
Map.groupBy(xs, f) // ES2024, Node 21+
xs.GroupBy(f) / xs.ToLookup(f)
d.setdefault(k, []).append(x)
let a = m.get(k); if (!a) m.set(k, (a = [])); a.push(x);
if (!d.TryGetValue(k, out var a)) d[k] = a = []; a.Add(x);
seen.add((r, c)) # tuples hash
seen.add(`${r},${c}`)
// or r * COLS + cnew HashSet<(int, int)>() // value tuples hash by value
TypeError: unhashable; use tuple(xs) or frozenset(xs)
xs.join(",")string.Join(",", xs), or a record
with value equalityfor i, x in enumerate(xs):
for (const [i, x] of xs.entries())
foreach (var (i, x) in xs.Index()) // .NET 9+ xs.Select((x, i) => ...)
zip(a, b, strict=True)
a.map((x, i) => [x, b[i]])
a.Zip(b)
range(n), range(n - 1, -1, -1)
for (let i = n - 1; i >= 0; i--)
Enumerable.Range(0, n) for (int i = n - 1; i >= 0; i--)
itertools.pairwise(xs)
for (let i = 1; i < xs.length; i++)
xs.Zip(xs.Skip(1))
itertools.combinations(xs, k) itertools.permutations(xs) itertools.product(a, b)
none: write the recursion
none: write the recursion
itertools.accumulate(xs) itertools.batched(xs, n) # 3.12+
manual loop
manual loop xs.Chunk(n) // .NET 6+
any(f(x) for x in xs), all(...)
xs.some(f), xs.every(f)
xs.Any(f), xs.All(f)
max(xs, key=f) max(xs, default=0)
Math.max(...xs) // RangeError // past ~10⁵ items: use a loop
xs.MaxBy(f) // .NET 6+ xs.Max() // throws on empty
The five biggest differences
Section titled “The five biggest differences”| Python | TypeScript | C# | |
|---|---|---|---|
| Heap | heapq, a min-heap over a list |
none: write one | PriorityQueue<TElement, TPriority>, min on priority, not stable |
| Sorted map / set | none in stdlib (bisect on a list) |
none | SortedSet<T>, SortedDictionary<K, V> |
| Integer size | unbounded | exact to 2⁵³ − 1, then bigint |
int 32-bit, wraps silently |
-7 / 2 as an integer |
-7 // 2 == -4 (floor) |
Math.trunc(-7 / 2) === -3 |
-7 / 2 == -3 (truncate) |
| Composite keys | tuples hash by value | arrays compare by reference | value tuples hash by value |
Heaps and priority queues
Section titled “Heaps and priority queues”Python and C# ship a min-heap. TypeScript doesn’t, so learn a 30-line one well enough to write it in five minutes. Ties are the subtle part: add a sequence number so equal priorities come out first-in, first-out. For when to use one, see heaps and priority queues.
import heapq
if __name__ == "__main__": # heapq is a MIN-heap over a plain list. Tuples compare left to right, # so (priority, seq, item) gives "lowest priority first, then FIFO on ties". pq: list[tuple[int, int, str]] = [] for seq, (prio, name) in enumerate([(2, "write"), (1, "read"), (2, "sync"), (0, "boot")]): heapq.heappush(pq, (prio, seq, name)) # seq breaks ties and avoids comparing names order = [heapq.heappop(pq)[2] for _ in range(len(pq))] assert order == ["boot", "read", "write", "sync"]
# Max-heap: negate the key (works everywhere) ... xs = [5, 1, 9, 3] neg = [-x for x in xs] heapq.heapify(neg) # O(n), in place assert -heapq.heappop(neg) == 9 # ... or, on Python 3.14+, use the new *_max functions. if hasattr(heapq, "heapify_max"): mx = xs[:] heapq.heapify_max(mx) assert heapq.heappop_max(mx) == 9
assert heapq.nsmallest(2, xs) == [1, 3] and heapq.nlargest(2, xs) == [9, 5] print("ok")import assert from "node:assert/strict";
/** JS/TS has no built-in heap. This is the minimal binary heap worth memorising (~25 lines). */export class Heap<T> { private a: T[] = []; constructor(private less: (x: T, y: T) => boolean) {} get size(): number { return this.a.length; } peek(): T | undefined { return this.a[0]; } push(x: T): void { const a = this.a; a.push(x); for (let i = a.length - 1; i > 0; ) { // sift up: swap with parent while smaller const p = (i - 1) >> 1; if (!this.less(a[i], a[p])) break; [a[i], a[p]] = [a[p], a[i]]; i = p; } } pop(): T | undefined { const a = this.a; if (a.length === 0) return undefined; const top = a[0], last = a.pop()!; if (a.length > 0) { a[0] = last; for (let i = 0; ; ) { // sift down: swap with the smaller child while it is smaller const l = 2 * i + 1, r = l + 1; let m = i; if (l < a.length && this.less(a[l], a[m])) m = l; if (r < a.length && this.less(a[r], a[m])) m = r; if (m === i) break; [a[i], a[m]] = [a[m], a[i]]; i = m; } } return top; }}
// Lowest priority first, FIFO on ties (seq), same as Python's (priority, seq, item) tuples.type Task = { prio: number; seq: number; name: string };const pq = new Heap<Task>((x, y) => x.prio !== y.prio ? x.prio < y.prio : x.seq < y.seq);const tasks: [number, string][] = [[2, "write"], [1, "read"], [2, "sync"], [0, "boot"]];tasks.forEach(([prio, name], seq) => pq.push({ prio, seq, name }));const order: string[] = [];while (pq.size > 0) order.push(pq.pop()!.name);assert.deepEqual(order, ["boot", "read", "write", "sync"]);
const max = new Heap<number>((x, y) => x > y); // max-heap: flip the comparison[5, 1, 9, 3].forEach((x) => max.push(x));assert.equal(max.pop(), 9);console.log("ok");// PriorityQueue<TElement, TPriority> (.NET 6+) is a MIN-heap on the priority.// It is not stable: equal priorities come out in no guaranteed order, so add a sequence number.var pq = new PriorityQueue<string, (int Prio, int Seq)>();(int, string)[] tasks = [(2, "write"), (1, "read"), (2, "sync"), (0, "boot")];for (int seq = 0; seq < tasks.Length; seq++) pq.Enqueue(tasks[seq].Item2, (tasks[seq].Item1, seq)); // value tuples compare item by itemvar order = new List<string>();while (pq.TryDequeue(out var name, out _)) order.Add(name);if (!order.SequenceEqual(["boot", "read", "write", "sync"])) throw new Exception("order");
// Max-heap: pass a reversed comparer for the priority type.var max = new PriorityQueue<int, int>(Comparer<int>.Create((x, y) => y.CompareTo(x)));foreach (int x in new[] { 5, 1, 9, 3 }) max.Enqueue(x, x);if (max.Peek() != 9) throw new Exception("max");
// No decrease-key. .NET 9+ has Remove(element, out _, out _), but it is a linear scan,// so the usual trick is still "push the new priority, skip stale entries on pop".if (!max.Remove(1, out _, out _) || max.Count != 3) throw new Exception("remove");Console.WriteLine("ok");Numbers: overflow, division, rounding
Section titled “Numbers: overflow, division, rounding”This is where the three languages disagree most, and where bugs are silent. See also math and number handling and money arithmetic.
import mathimport sys
if __name__ == "__main__": # Integers never overflow: they grow as needed. assert 2**63 + 1 == 9223372036854775809 assert sys.maxsize == 2**63 - 1 # largest list index on 64-bit, not an int limit
# // FLOORS (rounds toward minus infinity) and % takes the sign of the divisor. assert -7 // 2 == -4 and -7 % 2 == 1 assert divmod(-7, 2) == (-4, 1) # always a == b * q + r # Truncation toward zero (what C# and JS do) needs math.trunc or int() on a float... assert math.trunc(-7 / 2) == -3 # ...but / is float division: exact only up to 2**53. assert (10**17 + 1) // 1 != int((10**17 + 1) / 1)
assert -(-7 // 2) == 4 # ceil division without floats assert float("inf") > 10**100 # a safe "infinity" sentinel for min/max assert math.isclose(0.1 + 0.2, 0.3) # never compare floats with == print("ok")import assert from "node:assert/strict";
// number is a 64-bit float: integers are exact only up to 2^53 - 1.assert.equal(Number.MAX_SAFE_INTEGER, 2 ** 53 - 1);assert.equal(2 ** 53 + 1, 2 ** 53); // silently rounded, no errorassert.equal(2n ** 64n, 18446744073709551616n); // bigint when you need exact big integers
// / is float division. Math.trunc rounds toward zero, Math.floor toward minus infinity.assert.equal(Math.trunc(-7 / 2), -3);assert.equal(Math.floor(-7 / 2), -4);// % takes the sign of the DIVIDEND (like C#, unlike Python).assert.equal(-7 % 2, -1);const mod = (a: number, m: number) => ((a % m) + m) % m; // always in [0, m)assert.equal(mod(-7, 2), 1);
// Bitwise operators convert to 32-bit signed ints first.assert.equal(2 ** 31 | 0, -(2 ** 31));assert.equal(7 >> 1, 3); // fine for small non-negative ints, wrong past 2^31
assert.ok(Infinity > Number.MAX_VALUE); // sentinel for min/maxassert.ok(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON); // never compare floats with ===console.log("ok");// int is 32-bit, long is 64-bit. Arithmetic overflow WRAPS silently by default (unchecked).int big = int.MaxValue;if (unchecked(big + 1) != int.MinValue) throw new Exception("wrap");try { checked { _ = big + int.Parse("1"); } throw new Exception("should have thrown"); }catch (OverflowException) { } // checked turns wrap into an exceptionif ((long)big + 1 != 2147483648L) throw new Exception("widen first");
// Integer / truncates toward zero, and % takes the sign of the DIVIDEND (like JS).if (-7 / 2 != -3 || -7 % 2 != -1) throw new Exception("trunc");if (Math.DivRem(-7, 2) != (-3, -1)) throw new Exception("divrem");// Floor division and a non-negative modulo have to be written by hand.static int FloorDiv(int a, int b) => a / b - ((a % b != 0) && ((a < 0) != (b < 0)) ? 1 : 0);static int Mod(int a, int m) => ((a % m) + m) % m;if (FloorDiv(-7, 2) != -4 || Mod(-7, 2) != 1) throw new Exception("floor");
// Sentinels: int.MaxValue + anything overflows, so use long or check before adding.if (Math.Abs(0.1 + 0.2 - 0.3) > 1e-9) throw new Exception("float");decimal cents = 0.1m + 0.2m; // decimal is base-10: exact for moneyif (cents != 0.3m) throw new Exception("decimal");Console.WriteLine("ok");Rounding 2.5 is a classic: Python’s round() and C#’s Math.Round() use round half to even (2.5 → 2, 3.5 → 4) by default, while JavaScript’s Math.round() rounds half up toward +∞ (2.5 → 3, −2.5 → −2). For money, round explicitly with the rule the business wants.
Counting, grouping and composite keys
Section titled “Counting, grouping and composite keys”Counter, Map with ?? 0, and GetValueOrDefault are the three spellings of the same idea. The trap is the key: a JS array or a C# int[] is compared by reference, so [1, 2] as a key never matches another [1, 2]. Use a string key in TypeScript and a value tuple in C#. See hash maps and counting.
from collections import Counter, defaultdict
if __name__ == "__main__": words = "the cat and the hat and the bat".split()
# Counting: Counter is a dict subclass; missing keys read as 0. counts = Counter(words) assert counts["the"] == 3 and counts["dog"] == 0 # Sort by count descending, then word ascending: a tuple key does both. ranked = sorted(counts.items(), key=lambda kv: (-kv[1], kv[0])) assert ranked[:2] == [("the", 3), ("and", 2)]
# Grouping: defaultdict(list) creates the empty list on first access. by_len: defaultdict[int, list[str]] = defaultdict(list) for w in words: by_len[len(w)].append(w) assert by_len[3][:3] == ["the", "cat", "and"]
# Tuples are hashable, so they work as dict keys and set members (lists don't). seen: set[tuple[int, int]] = {(0, 0), (1, 2)} assert (1, 2) in seen grid_cost = {(r, c): r * 10 + c for r in range(3) for c in range(3)} assert grid_cost[(2, 1)] == 21 print("ok")import assert from "node:assert/strict";
const words = "the cat and the hat and the bat".split(" ");
// Counting: Map + "?? 0". Map keeps insertion order and allows any key type.const counts = new Map<string, number>();for (const w of words) counts.set(w, (counts.get(w) ?? 0) + 1);assert.equal(counts.get("the"), 3);// Sort by count descending, then word ascending (compare strings with < / >, not subtraction).const ranked = [...counts].sort(([a, x], [b, y]) => y - x || (a < b ? -1 : a > b ? 1 : 0));assert.deepEqual(ranked.slice(0, 2), [["the", 3], ["and", 2]]);
// Grouping: Map.groupBy (ES2024, Node 21+), or the manual get-or-create loop.const byLen = Map.groupBy(words, (w) => w.length);assert.deepEqual(byLen.get(3)?.slice(0, 3), ["the", "cat", "and"]);
// PITFALL: arrays are compared by reference, so they are useless as Map keys or Set members.const bad = new Map<number[], number>([[[1, 2], 9]]);assert.equal(bad.get([1, 2]), undefined); // a different array object// Fix: encode the tuple as a string (or a number like r * COLS + c for grids).const key = (r: number, c: number) => `${r},${c}`;const seen = new Set([key(0, 0), key(1, 2)]);assert.ok(seen.has(key(1, 2)));console.log("ok");string[] words = "the cat and the hat and the bat".Split(' ');
// Counting: GetValueOrDefault, or CountBy (.NET 9+).var counts = new Dictionary<string, int>();foreach (var w in words) counts[w] = counts.GetValueOrDefault(w) + 1;if (counts["the"] != 3) throw new Exception("count");if (words.CountBy(w => w).First(kv => kv.Key == "and").Value != 2) throw new Exception("countby");
// Sort by count descending, then word ascending. OrderBy is stable; use Ordinal for code-point order.var ranked = counts.OrderByDescending(kv => kv.Value).ThenBy(kv => kv.Key, StringComparer.Ordinal).ToList();if (ranked[0].Key != "the" || ranked[1].Key != "and") throw new Exception("rank");
// Grouping: GroupBy / ToLookup.var byLen = words.ToLookup(w => w.Length);if (!byLen[3].Take(3).SequenceEqual(["the", "cat", "and"])) throw new Exception("group");
// Value tuples have structural equality and hashing: ideal composite keys.var seen = new HashSet<(int R, int C)> { (0, 0), (1, 2) };if (!seen.Contains((1, 2))) throw new Exception("tuple key");// Arrays do NOT: int[] uses reference equality, same pitfall as JS.var bad = new HashSet<int[]> { new[] { 1, 2 } };if (bad.Contains(new[] { 1, 2 })) throw new Exception("arrays compare by reference");Console.WriteLine("ok");Queues, deques and stacks
Section titled “Queues, deques and stacks”A FIFO queue is the engine of BFS. Python’s list.pop(0) and JavaScript’s shift() move every element, so a BFS that uses them is quadratic. Use deque, a head index, or Queue<T>. See queues and deques and stacks.
from collections import deque
if __name__ == "__main__": # deque: O(1) at both ends. list.pop(0) is O(n), so never use a list as a queue. q: deque[int] = deque([1, 2, 3]) q.append(4) # push right q.appendleft(0) # push left assert q.popleft() == 0 and q.pop() == 4 assert q[0] == 1 and q[-1] == 3 # peek both ends (indexing the middle is O(n))
# maxlen makes a bounded buffer: appending to a full deque drops from the other end. last3: deque[int] = deque(maxlen=3) for x in range(6): last3.append(x) assert list(last3) == [3, 4, 5]
# Stack: a plain list. append / pop at the end are O(1). stack = [1, 2] stack.append(3) assert stack.pop() == 3 and stack[-1] == 2 print("ok")import assert from "node:assert/strict";
// JS has no deque. arr.shift() is O(n) in the worst case, so a BFS that shifts is quadratic.// For a FIFO queue, keep a head index and never shift.class Queue<T> { private items: T[] = []; private head = 0; get size(): number { return this.items.length - this.head; } push(x: T): void { this.items.push(x); } shift(): T | undefined { if (this.head === this.items.length) return undefined; const x = this.items[this.head++]; if (this.head > 1024 && this.head * 2 > this.items.length) { // compact now and then this.items = this.items.slice(this.head); this.head = 0; } return x; }}
const q = new Queue<number>();[1, 2, 3].forEach((x) => q.push(x));assert.equal(q.shift(), 1);assert.equal(q.size, 2);
// Stack: a plain array. push / pop at the end are O(1); .at(-1) peeks.const stack = [1, 2];stack.push(3);assert.equal(stack.pop(), 3);assert.equal(stack.at(-1), 2);console.log("ok");// Queue<T>: FIFO with O(1) Enqueue / Dequeue. TryDequeue avoids the exception on empty.var q = new Queue<int>([1, 2, 3]);q.Enqueue(4);if (q.Dequeue() != 1 || q.Peek() != 2) throw new Exception("queue");
// No built-in deque. LinkedList<T> gives O(1) at both ends (with node allocations).var dq = new LinkedList<int>();dq.AddLast(1); dq.AddLast(2); dq.AddFirst(0);if (dq.First!.Value != 0 || dq.Last!.Value != 2) throw new Exception("deque");dq.RemoveFirst(); dq.RemoveLast();if (dq.Count != 1) throw new Exception("deque size");
// Stack<T>: LIFO. Note: enumerating a Stack yields top first.var stack = new Stack<int>([1, 2]);stack.Push(3);if (stack.Pop() != 3 || stack.Peek() != 2) throw new Exception("stack");if (!q.TryDequeue(out int next) || next != 2) throw new Exception("try");Console.WriteLine("ok");Sorting rules worth memorising
Section titled “Sorting rules worth memorising”- JavaScript’s default sort compares strings.
[10, 9, 1].sort()returns[1, 10, 9]. Always pass(a, b) => a - bfor numbers. - Stability. Python’s sort and JavaScript’s
Array.prototype.sort(since ES2019) are stable. C#’sArray.SortandList<T>.Sortare not (the docs call it an unstable introspective sort); LINQOrderBy/ThenByis stable. - Multi-key sorts. Python: a tuple key,
key=lambda p: (-p.score, p.name). TypeScript: chain comparisons with||. C#:OrderByDescending(...).ThenBy(...). - String order. Python and JS
<compare code points / UTF-16 units. C#’s default string comparison can be culture-aware, so passStringComparer.Ordinalwhen you mean “by character code”. Usea < b ? -1 : a > b ? 1 : 0in TS when you want the same order as Python;localeCompareis locale-aware. - Comparator overflow. In C#,
(a, b) => a - bcan overflow for large ints. Usea.CompareTo(b).
More in sorting and custom comparators and sorted containers.
Go deeper
Section titled “Go deeper”- Python:
heapq,collections,bisect,itertools, and the Sorting HOW TO. - TypeScript / JavaScript (MDN):
Array.prototype.sort,Map,Number.MAX_SAFE_INTEGER,Math.round. - .NET:
PriorityQueue<TElement, TPriority>,SortedSet<T>,List<T>.Sort,Math.Round.