Review Python and C++ syntax for coding interviews. Sign in for recall practice and your saved syntax history.
Python
Split string by whitespace
Parsing words from a sentence without worrying about extra spaces.
words = s.split()
split() without arguments separates a string on any whitespace and collapses repeated spaces. The result is a list of non-empty tokens, so you do not need to manually trim or filter empty strings. In interviews, this is the cleanest way to parse words from a sentence before counting, reversing, or scanning them. It walks the string once, so the work is O(N).
Complexity: O(N)
Split string by specific delimiter
Parsing CSV data or specific delimited logs.
parts = s.split(',')
split(delimiter) breaks the string wherever that exact delimiter appears. Unlike the default form, repeated delimiters can create empty strings, which matters for CSV-like input and path parsing. Use it when the separator is meaningful and should not be treated like generic whitespace. The scan is linear in the string length.
Complexity: O(N)
Join array of strings into one string
Constructing a final string from an array of computed parts.
res = '-'.join(words)
join() builds one string by placing the separator between every string in the iterable. The separator comes first, so "-".join(words) joins with dashes; every element must already be a string. In LC-style string construction, collect pieces in a list and join once instead of repeatedly doing +=. The total work is linear in the output size.
Complexity: O(N)
Remove leading and trailing whitespace
Cleaning up dirty input string data.
clean = s.strip()
strip() returns a new string with leading and trailing whitespace removed. It does not change the original string, and it does not remove whitespace from the middle. Reach for it when normalizing raw input before comparisons or parsing. It may inspect the string from both ends, so treat it as O(N).
Complexity: O(N)
Replace all occurrences of a substring
Sanitizing strings, e.g., removing all periods from an IP address.
new_s = s.replace('old', 'new')
replace(old, new) returns a new string where every occurrence of old has been substituted. Strings are immutable in Python, so the original value is unchanged. This is useful for quick sanitization, but for character-by-character transformations in a loop, a list builder can be clearer. The replacement pass is linear in the string size plus output size.
Complexity: O(N)
Find index of substring (returns -1 if not found)
Checking substring existence and location safely without exceptions.
idx = s.find('sub')
find() searches for a substring and returns its first index, or -1 when it is absent. The -1 sentinel lets you branch without handling an exception. Use it when missing is an expected case, such as scanning logs or checking patterns. Substring search can cost O(N*M) in the simple model.
Complexity: O(N*M)
Find index of substring (raises ValueError if not found)
When you are absolutely sure the substring exists and want it to fail fast if not.
idx = s.index('sub')
index() searches like find(), but raises ValueError when the substring is missing. That makes it useful only when the problem invariant guarantees the substring exists, or when failure should be loud. In interview code, find() is usually safer unless you have already checked membership. The search cost is the same substring-scan cost.
Complexity: O(N*M)
Check if string starts with prefix
Trie prefix matching problems or parsing specific command prefixes.
is_start = s.startswith('pre')
startswith(prefix) checks whether the string begins with the given prefix. It compares only the needed prefix length, so it avoids slicing a temporary string. This is handy in trie, command parsing, and prefix validation problems. The work is O(K), where K is the prefix length.
Complexity: O(K)
Check if string ends with suffix
File extension checking or suffix parsing.
is_end = s.endswith('suf')
endswith(suffix) checks whether the string has the given suffix without manually slicing. It compares only the suffix length and handles the boundary check for you. Use it for extension checks, suffix rules, or parsing tokens from the right. The cost is O(K), where K is the suffix length.
Complexity: O(K)
Convert string to upper, lower, or capitalized form
Normalizing strings before comparative operations (e.g., case-insensitive anagram verification).
u, l, c = s.upper(), s.lower(), s.capitalize()
upper(), lower(), and capitalize() return normalized copies of the string. They do not mutate s, so store the returned value when you need it later. In interviews, lower() is common before case-insensitive palindrome or anagram checks. Each conversion visits the characters it returns.
Complexity: O(N)
Check character types
Valid Palindrome problem, identifying numbers in string parsing.
b1, b2, b3 = char.isalpha(), char.isdigit(), char.isalnum()
isalpha(), isdigit(), and isalnum() classify a single character or whole string. On one character they are constant-time and read cleanly in pointer loops. They are especially useful when skipping punctuation in palindrome and parser problems. Remember that these methods are broader than just ASCII letters and digits.
Complexity: O(1) per char
Convert char to ASCII int and back
Using array indices [0-25] for alphabet instead of hash maps (e.g. ord(c) - ord('a')).
val, ch = ord('a'), chr(97)
ord() converts a character to its integer code point, and chr() converts a code point back to a character. The common LC pattern is ord(c) - ord("a") to map lowercase letters to indices 0 through 25. Use this when a fixed-size frequency array is simpler and faster than a dictionary. Each conversion is O(1).
Complexity: O(1)
Reverse a string using slicing
Quick palindrome checks: s == s[::-1].
rev = s[::-1]
s[::-1] creates a reversed copy of the string using slicing with a step of -1. It is concise for quick palindrome checks, but it allocates another string. For memory-sensitive variants, compare with two pointers instead. The slice takes O(N) time and O(N) space.
Complexity: O(N)
Format integers or variables into strings
Constructing complex string keys or output formats efficiently.
res = f'Value: {count}'
An f-string evaluates expressions inside braces and inserts their string forms into the result. The literal text stays outside braces, while variables or expressions go inside braces. It is useful for building readable composite keys or formatted output during debugging. The cost scales with the produced string length.
Complexity: O(N)
Add single item vs add multiple items
Building flat arrays iteratively without nested list elements.
arr.append(x); arr.extend([y, z])
append(x) adds x as one element, while extend(iterable) appends each element from the iterable. The common gotcha is append([y, z]) creates a nested list, but extend([y, z]) keeps the array flat. Use these when building results incrementally in traversal, backtracking, or parsing problems. append is amortized O(1), while extend costs O(K) for K added items.
Complexity: O(1) amortized, O(K) for extend
Insert item at specific index
Prepending to array (though deque is better for this!).
arr.insert(0, val)
insert(index, val) places val before the given index and shifts later elements right. Inserting at 0 is readable but expensive because every existing item moves. For interview queues or front-heavy operations, prefer deque. The shift makes this O(N).
Complexity: O(N)
Remove and return last item or item at index
Stack operations (pop()) or queue operations (pop(0), but use deque!).
last = arr.pop(); first = arr.pop(0)
pop() removes and returns the last element, while pop(i) removes and returns the element at index i. A list is excellent as a stack because pop() from the end is O(1). pop(0) works but shifts the whole list, so use deque.popleft() for queues. Indexed pop is O(N) in the worst case.
Complexity: O(1) last, O(N) index
Remove first occurrence of value
Deleting a specific element when the index is unknown.
arr.remove(val)
remove(val) deletes the first element equal to val. It searches by value, not index, and raises ValueError if the value is absent. Use it only when deleting a known value from a small or one-off list; for frequent deletions, a set or counter is usually better. The search and shift make it O(N).
Complexity: O(N)
In-place reverse vs iterator reverse
Reversing an array without taking extra memory (in-place).
arr.reverse() vs rev_iter = reversed(arr)
arr.reverse() reverses the list in place and returns None, while reversed(arr) returns an iterator over the elements backward. The in-place method saves memory when you no longer need the original order. The iterator is useful for loops without copying. Both traverse the list, but only the in-place version mutates it.
Complexity: O(N)
Sort list in-place
Sorting intervals or sorting by specific tuple elements to save memory.
arr.sort(key=lambda x: x[1], reverse=True)
sort() mutates the list in place and returns None. key=lambda x: x[1] tells Python to compare each element by index 1, and reverse=True would make the order descending. Use this for intervals, pairs, or objects when changing the original list is fine. Sorting costs O(N log N).
Complexity: O(N log N)
Return new sorted list from iterable
When you need to keep the original array intact but need a sorted version.
new_arr = sorted(arr)
sorted(arr) returns a new sorted list and leaves arr unchanged. It accepts the same key and reverse arguments as list.sort(), but it allocates a new list. Use it when the original order is still needed later in the solution. The sort is O(N log N) plus O(N) extra space.
Complexity: O(N log N) + O(N) space
Extract sublist
Creating copies of arrays (arr[:]) or getting middle portions.
sub = arr[start:end:step]
arr[start:end:step] copies a range using start inclusive, end exclusive, and an optional step. Missing bounds default to the beginning or end, so arr[:] is a shallow copy. Be careful inside recursion or loops because slicing allocates a new list. The cost is O(K), where K is the slice length.
Complexity: O(K) where K is slice length
Create filtered list concisely
Quick initializations or filtering without writing map/filter functions.
evens = [x for x in arr if x % 2 == 0]
A list comprehension builds a new list by evaluating the expression for each item that passes the optional if filter. The expression comes before the for clause, and the filter comes last. Use it for concise filtering or transformation when the logic fits on one readable line. It scans the input once and stores the output.
Complexity: O(N)
Iterate multiple lists simultaneously
Comparing character by character in two strings or traversing parallel arrays.
for a, b in zip(listA, listB):
zip(listA, listB) pairs elements by position and stops when the shortest iterable ends. The loop unpacks each pair into a and b, which keeps parallel-array comparisons tidy. Use it for character comparisons, coordinate pairs, or synchronized scans. If unequal lengths matter, use zip_longest instead.
Complexity: O(min(N, M))
Iterate with index and value
Almost every loop where you need the index (e.g., Two Sum hash map population).
for i, val in enumerate(arr):
enumerate(arr) yields each index together with its value. The index comes first in the unpacking pattern: for i, val in enumerate(arr). This avoids manual counter variables in hash map, two-sum, and result-building loops. It is a linear pass over the iterable.
Complexity: O(N)
Common list math operations
Getting bounds or totals instantly without manual iteration.
length, minimum, maximum, total = len(arr), min(arr), max(arr), sum(arr)
len() reads a stored list length, while min(), max(), and sum() scan the list. These helpers are good for simple bounds and totals before choosing a more complex pattern. Avoid calling min or max repeatedly inside a loop over the same unchanged array. len is O(1), but the others are O(N).
Complexity: O(1) len, O(N) others
Initialize 2D grid correctly
Dynamic Programming memorization tables or graph adjacency matrices.
grid = [[0] * cols for _ in range(rows)]
This creates a fresh inner list for each row of the grid. The comprehension is important: [[0] * cols] * rows aliases the same row object and causes updates to leak across rows. Use this for DP tables, visited matrices, and grid state. Initializing every cell is O(rows * cols).
Complexity: O(rows * cols)
Shallow vs Deep Copy
Backtracking recursion where you need to append a snapshot of the current path.
shallow = arr.copy(); import copy; deep = copy.deepcopy(arr)
copy() makes a shallow copy of the outer list, while deepcopy() recursively copies nested objects. In backtracking, path.copy() or path[:] is usually enough because path contains primitives. Use deepcopy only when nested mutable structures must be independent. Copying costs proportional to the copied structure.
Complexity: O(N) shallow, O(N) deep
Get value with fallback if key is missing
Counting frequencies without catching KeyError.
val = d.get(key, 0)
get(key, default) reads a dictionary value without raising KeyError when the key is absent. The default is returned but not inserted into the dictionary. It is perfect for frequency reads like counts.get(x, 0) + 1. Average lookup time is O(1).
Complexity: O(1)
Get dict keys, values, or entries
Iterating over hash map components.
k, v, i = d.keys(), d.values(), d.items()
keys(), values(), and items() return dynamic views over the dictionary contents. items() is the usual choice when you need both key and value in the same loop. These views avoid copying until you explicitly wrap them in list(). Iterating over them is O(N).
Complexity: O(N)
Merge another dictionary into current
Combining hash maps or mass-updating configurations.
d.update(other_d)
update(other_d) writes all key-value pairs from other_d into d. Existing keys are overwritten, and missing keys are inserted. Use it when merging lookup state or applying a batch of changes. The cost is O(K), where K is the number of incoming pairs.
Complexity: O(K)
Remove key and return value with default
Safely deleting entries when you aren't certain the key exists.
val = d.pop('key', default_val)
pop(key, default) removes a key and returns its value, falling back to default if the key is absent. Without the default, a missing key raises KeyError. This is useful in sliding-window and cache logic where deletion may be conditional. Average deletion is O(1).
Complexity: O(1)
Dictionary with default factory type
Building adjacency lists for graphs or grouping items.
from collections import defaultdict
d = defaultdict(list)
d[key].append(val)
defaultdict(list) creates an empty list automatically the first time a missing key is accessed. That lets d[key].append(val) work without an if key not in d branch. It is a strong fit for adjacency lists, grouping anagrams, and bucket maps. Each append is still average O(1).
Complexity: O(1)
Count frequencies of iterable elements
Top K Frequent Elements problem or checking string anagrams.
from collections import Counter
counts = Counter(arr)
top = counts.most_common(k)
Counter(arr) builds a frequency map from an iterable. most_common(k) returns the highest-count entries as (value, count) pairs, which is useful for Top K style problems. For anagrams, comparing Counters is often clearer than manual maps. Building is O(N), and extracting top k has extra heap or sorting cost.
Complexity: O(N) build, O(N log K) most_common
Create dictionary defensively and concisely
Mapping indices to values rapidly to create lookup tables.
d = {k: v for k, v in enumerate(arr)}
A dictionary comprehension builds key-value pairs in one expression. Here enumerate(arr) supplies index-value pairs, but the expression maps k to v, so check that direction matches your lookup need. Use this for quick index maps, reverse maps, or transformed lookup tables. It is O(N) time and space.
Complexity: O(N)
Check if key exists in dictionary
Checking for seen/visited nodes or checking target differences in Two Sum.
if key in d:
key in d checks whether a key exists in the dictionary. It tests keys only, not values. This is the standard guard before reading seen states, target complements, or visited nodes. Average membership testing is O(1).
Complexity: O(1)
Iterate key-value pairs
Finding max frequencies or filtering based on keys and values together.
for key, val in d.items():
d.items() lets a loop unpack each key and value together. This keeps code clean when the decision depends on both parts, such as finding the largest count or filtering entries. Do not mutate the dictionary size while iterating over it. The loop is O(N).
Complexity: O(N)
Add, explicitly remove, or safely discard elements
Tracking visited grid positions or unique seen values.
s.add(x); s.remove(y); s.discard(z)
add(x) inserts x, remove(y) deletes y and raises if y is absent, and discard(z) deletes z only if present. The remove vs discard difference matters in sliding-window code where state may already be gone. Use sets for visited cells, duplicate detection, and membership state. These operations are average O(1).
Complexity: O(1)
Union, intersection, difference operators
Finding common characters in strings (intersection) or unseen elements (difference).
u, i, d = s1 | s2, s1 & s2, s1 - s2
|, &, and - compute union, intersection, and difference between sets. These operators return new sets rather than mutating the originals. They are useful when the problem asks for overlap, missing elements, or shared character sets. The cost depends on the set sizes involved.
Complexity: O(min(len(s1), len(s2)))
Fast lookup
Longest Consecutive Sequence — O(1) checks to see if adjacent numbers exist.
if x in s:
x in s performs a hash lookup in the set. This is the key operation behind duplicate detection and Longest Consecutive Sequence. Use it when order does not matter and membership is the question. Average lookup is O(1).
Complexity: O(1)
Build set conditionally
Extracting unique elements that match criteria.
unique_evens = {x for x in arr if x % 2 == 0}
A set comprehension scans an iterable, filters values, and stores only unique results. The expression before for is what gets inserted, and the if clause decides which values survive. It is useful when deduplication and filtering happen together. The pass is O(N) average time.
Complexity: O(N)
Fast deduplication
Removing duplicates from an array.
unique_arr = list(set(arr))
set(arr) removes duplicates by hashing, and list(...) converts the unique values back to a list. The order is not guaranteed, so do not use this when original order matters. For order-preserving dedupe, track a seen set while building a result list. The conversion is O(N) average time and space.
Complexity: O(N)
Transform list into min heap in-place
Initializing a priority queue efficiently from existing data.
import heapq
heapq.heapify(arr)
heapq.heapify(arr) rearranges an existing list into a min-heap in place. After heapify, arr[0] is the smallest item, but the whole list is not sorted. Use this when you already have all values and want a priority queue faster than pushing one by one. Heapify is O(N).
Complexity: O(N)
Add element and extract minimum
Dijkstra's Algorithm or Prim's MST.
heapq.heappush(heap, val)
min_val = heapq.heappop(heap)
heappush adds a value while preserving heap order, and heappop removes and returns the smallest value. The heap is just a normal list managed by heapq functions. This is the core pattern for Dijkstra, scheduling, and top-k problems. Each push or pop is O(log N).
Complexity: O(log N)
Simulate max heap with negative values
Finding Kth largest element or managing median in a data stream.
heapq.heappush(max_heap, -val)
max_val = -heapq.heappop(max_heap)
Python heapq is a min-heap, so pushing negative values simulates a max-heap. Negate on insertion and negate again after popping to recover the original maximum. This is common for kth-largest and two-heap median patterns. Each operation keeps the O(log N) heap cost.
Complexity: O(log N)
Get multiple largest/smallest elements directly
Quick and readable Kth largest/smallest without writing the heap loop yourself.
top_k = heapq.nlargest(k, arr)
bot_k = heapq.nsmallest(k, arr)
nlargest(k, arr) and nsmallest(k, arr) return the k extreme values directly. They are concise when you only need the result, not an ongoing heap state. For repeated streaming updates, maintain your own heap instead. The typical cost is O(N log K).
Complexity: O(N log K)
Priority queue with tuples and tie-breaking
Sorting graph nodes by distance where distance is primary and node ID resolves ties.
heapq.heappush(pq, (priority, val, unique_id))
A heap of tuples orders by the first tuple element, then the second, and so on. Put the priority first, then the value or a unique tie-breaker when values may not be directly comparable. This is useful for graph distances, scheduled events, and stable tie handling. Push and pop remain O(log N).
Complexity: O(log N)
Initialize double ended queue
BFS traversal where Left/Right operations must be O(1).
from collections import deque
q = deque([1, 2, 3])
deque creates a double-ended queue from an optional iterable. Unlike a list, it supports efficient operations at both ends. Use it for BFS queues and sliding-window structures where popleft() is frequent. Building from N items is O(N).
Complexity: O(N)
Monotonic deque pattern
Maintaining sorted candidates. Pop right to maintain order.
while q and q[-1] > val: q.pop()
q.append(val)
This pattern pops from the right while the new value would break the deque order, then appends the new value. The deque stores only useful candidates, so each value is inserted and removed at most once. Use it for monotonic queue problems where you need fast window minima or ordered candidates. The amortized cost per element is O(1).
Complexity: O(1) amortized
O(1) append and pop from left
Processing Tree BFS level-by-level efficiently.
q.appendleft(val); first = q.popleft()
appendleft(val) adds to the front, and popleft() removes from the front. These are the queue operations lists do poorly at index 0. Use them for BFS and any process that consumes from the left. Both operations are O(1).
Complexity: O(1)
OrderedDict import and usage pattern for LRU Cache
LRU Cache implementation requiring O(1) pop from front and move to back.
from collections import OrderedDict
od = OrderedDict()
od.move_to_end(key)
oldest = od.popitem(last=False)
OrderedDict keeps keys in insertion order and can move a key to the end in O(1). move_to_end marks a key as recently used, and popitem(last=False) removes the oldest key. This is the classic Python shortcut for LRU Cache before relying on built-in decorators. The cache operations shown are O(1).
Complexity: O(1)
Sliding window maximum using deque
Maintaining monotonically decreasing candidates for Sliding Window Maximum.
while q and q[-1] < val: q.pop()
q.append(val)
This monotonic decreasing deque removes smaller values from the right before appending the current value. The front can then represent the best maximum candidate for the current window. In full solutions, also remove expired indices from the left. Each element enters and leaves at most once, so it is O(1) amortized per step.
Complexity: O(1) amortized
Using standard list as Stack
DFS traversals or Monotonic Stack problems (e.g., Daily Temperatures).
stack = []
stack.append(val)
last = stack.pop()
A Python list works well as a stack when you only use append() and pop() at the end. The last pushed value is the first popped value. Use this for iterative DFS, parentheses matching, and monotonic stack problems. Both operations are amortized O(1).
Complexity: O(1)
Sort by second element of tuple
Sorting specific interval endpoints (e.g. start/end times).
arr.sort(key=lambda x: x[1])
arr.sort(key=lambda x: x[1]) sorts the list in place by the second element of each item. The lambda receives each element and returns the comparison key. This is common for intervals, pairs, and heap-like records when one field drives ordering. Sorting is O(N log N).
Complexity: O(N log N)
Sort strings by length
Longest String Chain or Word Break variations where shortest must be processed first.
words.sort(key=len)
words.sort(key=len) sorts strings in place by their length. Passing len directly is cleaner than writing lambda w: len(w). Use it when processing shorter strings first unlocks DP or chain transitions. The sort is O(N log N), with key extraction done once per element.
Complexity: O(N log N)
Sort by multiple conditions (first ascending, second descending)
When sorting elements by a primary score, and then a secondary tie-breaker rule.
arr.sort(key=lambda x: (x[0], -x[1]))
Returning a tuple from key sorts by the first item, then uses the next item to break ties. In (x[0], -x[1]), the primary value is ascending and the secondary numeric value is descending. This is a common trick for envelopes, intervals, and ranking problems. The sort cost is O(N log N).
Complexity: O(N log N)
Use sorted() returning new array reversed
When dealing with objects and needing non-destructive descending sort.
res = sorted(arr, key=lambda x: x.score, reverse=True)
sorted() creates a new ordered list using the provided key and reverse flag. The lambda reads x.score for comparison, and reverse=True flips the final order. Use this when sorting objects or records without mutating the original iterable. It costs O(N log N) time and O(N) space.
Complexity: O(N log N) + O(N) space
Fast and slow pointer initialization for Floyd's cycle detection
Detecting cycles in linked lists or finding the middle of a linked list.
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast: return True
This initializes slow and fast pointers at the same node and advances fast twice as quickly. The while guard checks both fast and fast.next before touching fast.next.next. If the pointers meet, a cycle exists; if fast reaches the end, there is no cycle. The scan is O(N) with O(1) space.
Complexity: O(N)
Fixed size sliding window maintaining exactly k elements
Maximum sum subarray of size k, or anagram search of fixed length.
window = sum(arr[:k])
for i in range(k, len(arr)):
window += arr[i] - arr[i-k]
# update result
This builds the first window sum, then slides by adding the incoming element and subtracting the outgoing one. The index i is the new right edge, and i-k is the element leaving the window. Use it when every valid candidate has exactly k elements. The total work is O(N).
Complexity: O(N)
Standard opposite end two pointer initialization
Two Sum II (sorted array), trapping rain water, reversing string.
left, right = 0, len(arr) - 1
while left < right:
# logic
left and right start at opposite ends and move inward based on the problem rule. The loop condition left < right avoids crossing pointers. This is the standard shape for sorted two-sum, reversal, and container-style optimizations. Each pointer moves at most N times total.
Complexity: O(N)
Standard sliding window variable size
Longest substring without repeating characters, longest repeating character replacement.
left = 0
for right in range(len(arr)):
# add to window
while invalid_condition:
# remove left from window
left += 1
# update result
The right pointer expands the window, and the inner while loop shrinks from the left until the state is valid again. The update usually happens after restoring validity, but some problems update before shrinking. Use this for longest or shortest subarray problems with a maintainable window invariant. Because each pointer only moves forward, the pattern is O(N).
Complexity: O(N)
Find insertion point retaining sorted order
Finding first or last occurrence of a duplicate in a sorted array, or quick closest value lookups.
import bisect
idx_left = bisect.bisect_left(arr, val)
idx_right = bisect.bisect_right(arr, val)
bisect_left returns the first insertion index where val could go, while bisect_right returns the index after the last equal val. The array must already be sorted. These are ideal for boundary searches, duplicate ranges, and counting values below or above a threshold. Each lookup is O(log N).
Complexity: O(log N)
Classic explicit binary search template
Standard approach when you are searching for exactly one element.
l, r = 0, len(arr) - 1
while l <= r:
m = l + (r - l) // 2
if arr[m] == target: return m
elif arr[m] < target: l = m + 1
else: r = m - 1
This is the classic closed-interval binary search over indices l through r. The midpoint uses l + (r - l) // 2, then discards the half that cannot contain target. Use it when you need exact control over boundaries or return values. It runs in O(log N) time and O(1) space.
Complexity: O(log N)
Adjacency list initialization
Converting arrays of edges into a traversable graph structure.
adj = defaultdict(list)
for u, v in edges:
adj[u].append(v)
adj[v].append(u)
This builds an adjacency list from an edge list using defaultdict(list). For each undirected edge, both directions are appended; for directed graphs, only append u to v. This representation is the starting point for BFS, DFS, topological sort, and shortest path problems. Building it is O(V + E) when including the graph structure size.
Complexity: O(V + E)
BFS template with visited set for graphs with cycles
Shortest path in unweighted graphs or connected components in cyclic graphs.
q = deque([start])
visited = {start}
while q:
node = q.popleft()
for nei in adj[node]:
if nei not in visited:
visited.add(nei)
q.append(nei)
This BFS uses a deque for FIFO order and a visited set to avoid revisiting nodes in cyclic graphs. Marking a node visited when it is enqueued prevents duplicates from piling up in the queue. Use it for unweighted shortest paths and connected components. It visits each node and edge at most once.
Complexity: O(V + E)
Dijkstra shortest path combining min heap and visited set
Shortest path in a graph with non-negative edge weights.
pq = [(0, start)]
visited = set()
while pq:
dist, node = heapq.heappop(pq)
if node in visited: continue
visited.add(node)
for nei, weight in adj[node]:
heapq.heappush(pq, (dist + weight, nei))
This is Dijkstra with a min-heap of (distance, node) states. The visited check skips stale heap entries after the shortest distance to a node is finalized. Use it only with non-negative edge weights. The heap operations give the usual O(E log V) complexity.
Complexity: O(E log V)
Iterative DFS using an explicit stack
When recursion depth might exceed limits, or specifically avoiding the call stack.
stack = [root]
while stack:
node = stack.pop()
# visit node
if node.right: stack.append(node.right)
if node.left: stack.append(node.left)
This uses a list as an explicit stack instead of the Python call stack. Pushing right before left makes the left child get processed first because the stack is LIFO. Use it when recursion depth is risky or when you want explicit control over traversal order. It is O(V + E) with stack space for the frontier.
Complexity: O(V + E)
Standard BFS Level-by-level traversal
Binary Tree level order traversal, finding shortest path in unweighted graphs.
q = deque([root])
while q:
for _ in range(len(q)):
node = q.popleft()
if node.left: q.append(node.left)
if node.right: q.append(node.right)
This BFS processes one level at a time by looping over the queue length captured at the start of the level. New children are appended for the next level, not the current one. Use it for level-order traversal, minimum depth, and shortest unweighted paths. Each node and edge is processed once.
Complexity: O(V + E)
Standard DFS Recursion
Tree validations, paths, depth calculations using the call stack.
def dfs(node):
if not node: return
# visit node
dfs(node.left)
dfs(node.right)
The recursive DFS base case returns on a missing node, then visits and recurses into children. The call stack tracks the path for you, which makes tree depth, validation, and path problems concise. Be mindful of Python recursion limits on very deep trees or graphs. The traversal is O(V + E).
Complexity: O(V + E)
Standard LeetCode TreeNode class
Required for defining helper functions or mock testing locally.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
This is the standard LeetCode binary tree node shape: a value plus optional left and right child references. The default arguments make quick local tests easy. You usually do not submit this when LeetCode already provides it, but it helps for helpers and local mocks. Creating one node is O(1).
Complexity: N/A
Topological sort using BFS with in-degree counting
Course schedule dependencies or validating DAG structures (Kahn's Algorithm).
q = deque([n for n in range(num) if indegree[n] == 0])
while q:
node = q.popleft()
res.append(node)
for nei in adj[node]:
indegree[nei] -= 1
if indegree[nei] == 0: q.append(nei)
This is Kahn's algorithm for topological sorting. Nodes with indegree 0 enter the queue first, and removing a node reduces the indegree of its neighbors. Use it for course prerequisites and dependency ordering; if the result misses nodes, a cycle exists. It runs in O(V + E).
Complexity: O(V + E)
Efficient string concatenation loop
Avoiding massive overhead of res += char inside a loop (which allocates a new string every time).
res = []
for char in string:
res.append(char)
final = ''.join(res)
This pattern appends characters to a list and joins once at the end. It avoids creating a brand-new string on every loop iteration. Use it whenever output is assembled piece by piece, especially in parsing or backtracking. The final construction is O(N).
Complexity: O(N)
Integer division and Modulo
Extracting digits from an integer iteratively (val % 10, val // 10).
quotient = 10 // 3
remainder = 10 % 3
// gives integer floor division, and % gives the remainder. Together they are the standard way to peel digits with val % 10 and shrink the number with val // 10. Be careful with negative numbers because Python floor division rounds down. Each operation is O(1) for typical LC integer sizes.
Complexity: O(1)
Absolute value and modular exponentiation
Handling distances or keeping large exponent calculations within integer bounds.
val = abs(-5)
res = pow(base, exp, mod)
abs() returns distance from zero, which is useful for differences and coordinates. pow(base, exp, mod) performs modular exponentiation efficiently without building the enormous intermediate value. Use the three-argument pow for number theory or hashing problems. Modular exponentiation is O(log exp).
Complexity: O(1), O(log exp)
Max and Min values for variables
Initializing variables when looking for minimums or maximums in arrays.
pos_inf = float('inf')
neg_inf = float('-inf')
float("inf") and float("-inf") give sentinel values larger or smaller than any normal finite comparison target. They are useful for initializing best minimums and maximums before scanning. This avoids special-casing the first element. Creating the sentinels is O(1).
Complexity: O(1)
Convert string to int and back
Dealing with string-represented numbers safely.
num = int('42')
chars = str(42)
int("42") parses a numeric string, and str(42) turns a number back into characters. int() raises ValueError if the string is not a valid integer, so validate when input may be dirty. Use these for string arithmetic and encoded-number problems. The conversion scans the digits, so it is O(N).
Complexity: O(N)
Convert int to list of digits and back
Happy Number problem, processing each digit mathematically.
digits = [int(d) for d in str(num)]
num = int(''.join(map(str, digits)))
str(num) exposes the digits for iteration, int(d) converts each character back to a number, and join(map(str, digits)) rebuilds a numeric string. This is convenient when digit order matters more than arithmetic extraction. For very tight loops, repeated % 10 and // 10 may avoid string allocation. The conversion is O(N) in the number of digits.
Complexity: O(N)
Standard backtracking template (choose, explore, unchoose)
Combinations, permutations, subsets, and puzzle solving.
def backtrack(start, path):
if base_case:
res.append(path[:])
return
for i in range(start, len(choices)):
path.append(choices[i])
backtrack(i + 1, path)
path.pop()
This is the choose, explore, unchoose pattern. path.append chooses a candidate, the recursive call explores that choice, and path.pop restores state for the next candidate. res.append(path[:]) stores a snapshot so later mutations do not corrupt the answer. The complexity depends on the search tree, commonly O(2^N) or O(N!).
Complexity: O(2^N) or O(N!) depending on problem
1D DP array initialization and bottom-up iteration
Fibonacci sequence, climbing stairs, or 1D knapsack style problems.
dp = [0] * (n + 1)
dp[0] = base_val
for i in range(1, n + 1):
dp[i] = dp[i-1] + arr[i]
This initializes a one-dimensional DP table with room for base cases. dp[0] stores the known starting answer, and later states build from earlier states. Use it when each state depends on a small number of previous positions, such as climbing stairs or prefix-style transitions. The loop is O(N).
Complexity: O(N)
2D DP table initialization for grid or string comparison problems
Unique paths, longest common subsequence, edit distance.
dp = [[0] * cols for _ in range(rows)]
for r in range(1, rows):
for c in range(1, cols):
dp[r][c] = dp[r-1][c] + dp[r][c-1]
This creates a rectangular DP table with independent rows, then fills it by row and column. Each cell combines previously computed neighbor states such as top and left. Use it for grid paths, edit distance, and two-string comparisons. Filling every cell is O(rows * cols).
Complexity: O(rows * cols)
Memoization template using functools lru_cache
Top-down dynamic programming avoiding manual visited dicts.
from functools import lru_cache
@lru_cache(None)
def dp(i, j):
if base_case: return 0
return max(dp(i+1, j), dp(i, j+1))
lru_cache memoizes calls to dp(i, j), so repeated states return instantly after the first computation. The function arguments must be hashable, which is why indices are usually better than mutable lists. Use this for top-down DP when the recurrence is clearer than table order. Runtime becomes proportional to the number of reachable states times transition cost.
Complexity: O(states)
Dummy node initialization pattern
Whenever the head of a linked list might change (e.g., removing heads, merging).
dummy = ListNode(0)
dummy.next = head
curr = dummy
# traverse...
return dummy.next
A dummy node sits before the real head so operations at the head look like operations in the middle. curr can safely build or modify links starting from dummy, then dummy.next is the resulting head. Use this for remove, merge, and partition problems where the head may change. The setup itself is O(1).
Complexity: O(1)
Iterative linked list reversal template
Reversing entire lists or sublists (Reverse Linked List II or Palindrome Linked List).
prev, curr = None, head
while curr:
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
head = prev
This reverses links one node at a time while preserving the next node before overwriting curr.next. prev trails the reversed portion, and curr walks through the unreversed portion. It is the core pattern for reverse list, palindrome list, and sublist reversal. The pass is O(N) with O(1) extra space.
Complexity: O(N)
ListNode class definition matching standard LeetCode format
Standard node structure for all LeetCode linked list problems.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
This is the standard LeetCode singly linked list node with a value and a next pointer. Defaults make it easy to create test nodes without passing every argument. The platform usually provides this class, but knowing the shape helps when writing helpers. Creating one node is O(1).
Complexity: N/A
any() and all() with lambda examples
Quickly asserting facts about iterables without writing full loops.
is_valid = all(x > 0 for x in arr)
has_zero = any(x == 0 for x in arr)
all() returns True only if every generated condition is true, while any() returns True as soon as one condition is true. Both consume the generator lazily and short-circuit when the answer is known. Use them for compact validity checks when the predicate is simple. Worst-case time is O(N).
Complexity: O(N)
sys.setrecursionlimit with standard high value for deep recursion
Preventing RecursionError / Maximum recursion depth exceeded on large test cases.
import sys
sys.setrecursionlimit(2000)
setrecursionlimit raises Python's maximum call-stack depth for recursive solutions. It can prevent RecursionError on deep trees or DFS chains, but it does not make recursion memory-free. Use it carefully, and prefer iterative traversal if the depth may be extremely large. Setting the limit is O(1).
Complexity: O(1)
Unpacking operator used to pass a list as arguments
Passing array elements as arguments to functions (like zip, max) dynamically.
arr = [1, 2, 3]
print(*arr) # equivalent to print(1, 2, 3)
The * operator expands a list or iterable into separate positional arguments. print(*arr) behaves like print(1, 2, 3), and the same idea works with functions like max or zip. Use it when a function expects separate arguments rather than one list. Expansion touches each element, so it is O(N).
Complexity: O(N)
zip_longest from itertools with fillvalue example
Iterating over two lists of unequal length seamlessly.
from itertools import zip_longest
for a, b in zip_longest(l1, l2, fillvalue=0):
zip_longest pairs items from iterables until the longest one is exhausted. Missing values from shorter iterables are replaced with fillvalue. This is useful when comparing or combining uneven sequences without losing tail elements. The loop runs O(max(N, M)).
Complexity: O(max(N, M))
C++
Extract a substring
Extracting a portion of a string. Note that the second argument is LENGTH, not end index.
string sub = s.substr(start_pos, length);
Complexity: O(K) where K is length
Find index of substring
Checking substring existence and location safely.
size_t pos = s.find("target");
if (pos != string::npos) { // found }
Complexity: O(N*M)
Append character or string
Efficiently building strings during string manipulation tasks.
s.push_back('a'); // char
s += "bc"; // string
Complexity: O(1) amortized
Remove last character
Backtracking algorithms involving strings where you un-choose a character.
s.pop_back();
Complexity: O(1)
Compare strings
Dictionary order comparisons or exact equality checks.
if (s1 == s2) { ... }
int res = s1.compare(s2); // <0, 0, >0
Complexity: O(min(N, M))
Check character types
Valid Palindrome problem, identifying numbers in string parsing.
#include <cctype>
if (isalnum(c)) { ... }
// isalpha(c), isdigit(c), islower(c)
Complexity: O(1) per char
Convert character to lower/upper
Normalizing characters before comparative operations (e.g., case-insensitive anagram verification).
char lower = tolower(c);
char upper = toupper(c);
Complexity: O(1)
String to Integer (stoi) and Integer to String (to_string)
Dealing with string-represented numbers safely.
int num = stoi("42");
string s = to_string(42);
Complexity: O(N)
Initialize 1D and 2D vectors
Dynamic Programming memorization tables or graph adjacency matrices.
vector<int> v(size, init_val);
vector<vector<int>> grid(rows, vector<int>(cols, init_val));
Complexity: O(N) or O(rows * cols)
Add or remove from end
Stack operations using a vector, or building dynamic arrays.
v.push_back(val);
v.pop_back();
Complexity: O(1) amortized
Access first and last elements
Quick access to ends without v[0] or v[v.size() - 1].
int first = v.front();
int last = v.back();
Complexity: O(1)
Insert item at specific position
Inserting in middle (inefficient, requires shifting).
v.insert(v.begin() + index, val);
Complexity: O(N)
Remove elements at index or range
Deleting items from middle of vector.
v.erase(v.begin() + index);
v.erase(v.begin() + start, v.begin() + end);
Complexity: O(N)
Change vector size
Expanding or shrinking an already initialized vector dynamically.
v.resize(new_size, fill_value);
Complexity: O(N)
Iterate with constraints
Range-based for loops for cleanliness, reverse iterators for backwards traversal.
for (int x : v) { ... }
for (auto it = v.rbegin(); it != v.rend(); ++it) { ... }
Complexity: O(N)
Hash map vs Tree map
unordered_map for frequency counting (Two Sum). map for sorted keys (Intervals, Sweepline).
unordered_map<int, int> umap; // Hash Map O(1)
map<int, int> omap; // BST Map O(log N)
Complexity: O(1) vs O(log N)
operator[] default insert, increment, and overwrite
Frequency counting and updates when you want missing keys to auto-initialize.
int x = umap[key]; // missing key is inserted with value 0
umap[key]++; // 0 -> 1 for new key
umap[key] = val; // explicit insert or overwrite
Complexity: O(1) amortized
Check if key exists safely
Checking for seen targets without inserting an empty element (which umap[key] does).
if (umap.find(key) != umap.end()) { // found }
Complexity: O(1) amortized
Check existence and count
Simpler syntax for checking if key exists, returns 1 or 0.
if (umap.count(key) > 0) { // found }
Complexity: O(1) amortized
Hash set vs Tree set
Tracking visited nodes (uset). Maintaining dynamically sorted streaming elements (oset).
unordered_set<int> uset; // Hash Set O(1)
set<int> oset; // BST Set O(log N)
Complexity: O(1) vs O(log N)
Add or remove elements
Tracking uniqueness and managing sliding windows of unique chars.
uset.insert(val);
uset.erase(val);
Complexity: O(1) amortized
Double ended queue operations
Sliding window maximum or BFS where insertions are needed front and back.
#include <deque>
deque<int> dq;
dq.push_front(v); dq.push_back(v);
dq.pop_front(); dq.pop_back();
int f = dq.front(); int b = dq.back();
Complexity: O(1)
Standard Queue operations
Standard BFS Level-order traversal.
#include <queue>
queue<int> q;
q.push(x);
q.pop(); // DOES NOT RETURN VALUE
int top = q.front();
Complexity: O(1)
Standard Stack operations
Iterative DFS traversals or Monotonic Stack problems (e.g., Daily Temperatures).
#include <stack>
stack<int> s;
s.push(x);
s.pop(); // DOES NOT RETURN VALUE
int top = s.top();
Complexity: O(1)
Default Max Heap
Finding Kth smallest elements (keep K max items).
#include <queue>
priority_queue<int> maxHeap;
maxHeap.push(val);
int mx = maxHeap.top();
maxHeap.pop();
Complexity: O(log N)
Standard Min Heap structure
Dijkstra's Algorithm or finding Kth largest elements.
priority_queue<int, vector<int>, greater<int>> minHeap;
Complexity: O(log N)
Heap with pairs (sorts by first, then second)
Dijkstra's shortest path combining min heap and visited set.
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
pq.push({dist, node});
Complexity: O(log N)
Priority queue with custom struct comparator
Merge K Sorted Lists, sorting custom object pointers dynamically.
struct Compare { bool operator()(ListNode* a, ListNode* b) { return a->val > b->val; } };
priority_queue<ListNode*, vector<ListNode*>, Compare> pq;
Complexity: O(log N)
Sort vector
Sorting arrays prior to Two Pointers or Binary Search.
#include <algorithm>
sort(v.begin(), v.end());
sort(v.rbegin(), v.rend()); // desc
Complexity: O(N log N)
Sort with custom lambda comparator
Sorting intervals or sorting by specific structured elements.
sort(intervals.begin(), intervals.end(), [](const vector<int>& a, const vector<int>& b) {
return a[1] < b[1]; // sort by end time
});
Complexity: O(N log N)
Reverse array or string
Reversing path results, string palindromes.
reverse(v.begin(), v.end());
Complexity: O(N)
Max, min, and abs
Updating max/min bounds safely in O(1).
int mx = max(a, b); // Note: must be same type
int mt = max({a, b, c}); // multi max
int diff = abs(a - b);
Complexity: O(1)
Swap two elements safely
In-place array manipulations (e.g. Partitioning in QuickSort, Dutch National Flag).
swap(a, b);
swap(v[i], v[j]);
Complexity: O(1)
Lower bound (first element >= target)
Finding exact matches or first element not smaller than target in log N.
auto it = lower_bound(v.begin(), v.end(), target);
int idx = it - v.begin(); // get integer index
Complexity: O(log N)
Upper bound (first element > target)
Finding strict upper boundaries in log N time.
auto it = upper_bound(v.begin(), v.end(), target);
int idx = it - v.begin();
Complexity: O(log N)
Check if element exists natively
Fast existence checks on sorted vectors when index isn't needed.
bool exists = binary_search(v.begin(), v.end(), target);
Complexity: O(log N)
Split string by spaces (StringStream)
Parsing words from a sentence without manual pointer management.
#include <sstream>
stringstream ss(s);
string word;
while (ss >> word) { ... }
Complexity: O(N)
Split string by custom delimiter
Parsing CSV data, dates, or file paths.
stringstream ss(s);
string part;
while (getline(ss, part, '-')) { ... }
Complexity: O(N)
Sum of a vector or array
Quickly finding the total sum without a manual loop.
#include <numeric>
int sum = accumulate(v.begin(), v.end(), 0);
// use 0LL for long long
Complexity: O(N)
Fill vector with sequentially increasing values
Initializing parent arrays for Disjoint Set Union (Union Find).
#include <numeric>
iota(v.begin(), v.end(), 0); // 0, 1, 2, 3...
Complexity: O(N)
Fill array or vector with specific value
Initializing DP tables to -1.
fill(v.begin(), v.end(), -1);
memset(arr, -1, sizeof(arr)); // C-style arrays only
Complexity: O(N)
Pair initialization and access
Grouping two elements without defining a custom struct. Often used for BFS states.
pair<int, int> p = make_pair(1, 2);
pair<int, string> p2 = {1, "a"};
int f = p.first; int s = p.second;
Complexity: O(1)
Structured bindings (C++17)
Clean iteration over hash maps or unpacking pairs instantly.
auto [u, v] = my_pair;
for (auto& [key, val] : umap) { ... }
Complexity: O(1)
Multiset initialization and count based erase
Sliding window problems tracking dynamic minimum/maximum values, allowing duplicates.
multiset<int> ms;
ms.insert(5); ms.insert(5);
ms.erase(ms.find(5)); // erases ONLY ONE 5
ms.erase(5); // erases ALL 5s
Complexity: O(log N)
Find Kth element (Quickselect)
Finding the median or Kth largest/smallest element in O(N) average time.
nth_element(v.begin(), v.begin() + K, v.end());
int kth = v[K];
Complexity: O(N) average
Partial sort
Extracting the top K elements fully sorted in O(N log K) time.
partial_sort(v.begin(), v.begin() + K, v.end());
Complexity: O(N log K)
Bit manipulation builtin macros
Counting number of 1 bits, leading zeros, or trailing zeros cleanly.
int ones = __builtin_popcount(n);
int lz = __builtin_clz(n);
int tz = __builtin_ctz(n);
// use __builtin_popcountll for long long
Complexity: O(1)
Greatest Common Divisor and Least Common Multiple
Fraction reduction or determining cycles.
#include <numeric>
int g = std::gcd(a, b); // C++17
int l = std::lcm(a, b);
Complexity: O(log(min(a, b)))
Dummy node setup to handle head edge-cases
Whenever head of list might be removed, shifted, or lists are merged.
ListNode dummy(0);
dummy.next = head;
ListNode* curr = &dummy;
return dummy.next;
Complexity: O(1)
Sliding window string tracking pattern
Longest substring without repeating characters, longest repeating replacement.
int left = 0, res = 0;
for (int right = 0; right < s.size(); right++) {
// add s[right] to state
while (invalid_state) {
// remove s[left] from state
left++;
}
res = max(res, right - left + 1);
}
Complexity: O(N)
Adjacency list from edge arrays
Initializing graph representations for BFS/DFS.
vector<vector<int>> adj(n);
for (auto& edge : edges) {
adj[edge[0]].push_back(edge[1]);
adj[edge[1]].push_back(edge[0]);
}
Complexity: O(V + E)
Level order Graph BFS
Finding shortest path in unweighted graphs or connected components.
queue<int> q;
q.push(start);
vector<bool> visited(n, false);
visited[start] = true;
while (!q.empty()) {
int curr = q.front(); q.pop();
for (int nei : adj[curr]) {
if (!visited[nei]) {
visited[nei] = true;
q.push(nei);
}
}
}
Complexity: O(V + E)