Loading...
Loading...
Python interview guide
Syntax, data types, control flow, functions, strings and error handling. Every answer is short enough to say aloud, and every question links to the lesson that teaches it.
60 questionsLast updated
Decide first what the first and the last value of the counter should be. Then check the condition against the last value: <= includes it and < leaves it out. Tracing the first and the last round by hand on paper catches most mistakes.
When a for loop executes for x in obj:, Python calls iter(obj) to obtain an iterator. On each iteration, it calls next() on the iterator. When items are exhausted, the iterator raises StopIteration, which Python catches internally to cleanly exit the loop.
It keeps the arithmetic simple: range(a, b) has exactly b - a numbers, and range(0, 3) followed by range(3, 6) joins with no overlap and no gap. It also matches how indexes work in Python sequences, which start at 0.
Use range() when you need numbers themselves, such as a count of repeats or values with a step. When you only need each item of a sequence, loop over it directly, because there is no counter to get wrong.
Lesson: The for Loop and range()The most Pythonic way to break out of multiple nested loops is to encapsulate the loops inside a function and use a return statement, which immediately exits all loop levels and returns control to the caller.
break ends the whole loop, and the program goes on after the loop. continue ends only the current round, and the loop goes on with the next one. For example, in a loop over order numbers, break stops at the first bad order, while continue skips it and processes the rest.
Each nested loop level typically multiplies the time complexity by the range of that loop. Two nested loops of size N yield O(N²), while three yield O(N³). In technical coding interviews, optimizing nested O(N²) loops down to O(N) or O(N log N) using hash tables or two pointers is a primary evaluation metric.
Lesson: Nested Loops and Grid TraversalMake the inner loop start at i + 1: for i in range(n): for j in range(i + 1, n):. This executes exactly N*(N-1)/2 iterations, avoiding duplicate symmetric pairs and self-comparisons.
When the counts are fixed, the inner body runs m * n times. When the inner count depends on the outer variable, such as range(i), you add the inner counts for each round. For i from 0 to m - 1 that is 0 + 1 + ... + (m - 1), which is m * (m - 1) / 2.
Naive recursive Fibonacci fib(n) = fib(n-1) + fib(n-2) has exponential time complexity O(2^N) due to redundant recalculations. Iterative Fibonacci with two variables operates in linear time O(N) and constant space O(1).
Deterministic primality testing for arbitrary integers cannot run in O(1) time. However, for a fixed range (e.g. up to 10^7), the Sieve of Eratosthenes can precompute all primes in O(N log log N) time, allowing subsequent individual queries to be answered in O(1) time via lookup.
Lesson: Loops CheckpointIt repeats a, b = b, a % b until b is 0, and then a is the answer. It works because gcd(a, b) equals gcd(b, a % b), and the remainder gets smaller every round, so the loop always ends.
Yes. In Python, functions are instances of the function class. They can be assigned to variables, passed as arguments to other functions, returned from functions, and stored in data structures (such as lists and dictionaries). This enables functional programming paradigms and decorators.
Function docstrings are stored in the function object's __doc__ attribute (e.g. func.__doc__). Tools like Sphinx, pydoc, and FastAPI use this attribute to auto-generate API documentation and interactive Swagger interfaces.
A pure function gives the same result for the same arguments and changes nothing outside itself: no prints, no globals, no files. You can test it with a plain call and an equality check, without setting up or cleaning anything.
Lesson: Defining and Calling FunctionsIntroduced in Python 3.8 (PEP 570), / marks parameters before it as positional-only (they cannot be passed by keyword). The * symbol marks parameters after it as keyword-only (they must be passed by keyword, e.g. def f(a, /, b, *, c):). This lets API authors preserve freedom to rename internal parameters.
A call such as create_user(role="admin", active=True) explains itself and cannot silently swap two values of the same type. Adding a new optional parameter later also does not break calls that already use names.
At call time, before the function body runs. Python tries to match every argument to a parameter; if one is missing, extra or given twice, it raises a TypeError such as "missing 1 required positional argument".
Lesson: Parameters and Arguments*args, and a dictionary into **kwargs, at the call site?Use the single asterisk * to unpack a sequence such as a tuple into positional arguments (func(*values)), and the double asterisk ** to unpack a dictionary into keyword arguments (func(**options)). This is called argument unpacking.
Put it after * or after *args in the def line, for example def f(a, *, strict=False). The caller must then write f(1, strict=True). This keeps flags readable and stops them from being passed by position by mistake.
A closure occurs when a nested function retains access to variables in its Enclosing scope even after the outer function has completed execution and returned. Python captures the enclosing variables inside the inner function's __closure__ attribute as cell objects.
global and nonlocal keywords?global tells Python that an identifier belongs to the top-level module (Global) scope. nonlocal (introduced in PEP 3104) binds a variable to the nearest Enclosing scope of a nested function, allowing inner functions to modify state in an enclosing function without polluting the global namespace.
Any function can change a global, so a bug can come from anywhere and a function's result depends on hidden state. Tests need setup and cleanup, and threads can race. Passing values in as arguments and returning results keeps behaviour visible and testable.
Lesson: Scope (LEGB Rule) and Lambda FunctionsGuido van Rossum argued that TCO obscures tracebacks during debugging by removing intermediate stack frames. Additionally, recursion is not the primary mechanism for iteration in Python (unlike Scheme or Haskell); idiomatic Python favors generator functions, comprehensions, and iterative loops.
Lesson: Functions CheckpointYou can either: 1) Convert the recursive algorithm to an iterative version using an explicit list as a stack in the heap; 2) Apply memoization (functools.lru_cache) to avoid redundant subproblems; or 3) Increase the stack limit with sys.setrecursionlimit() if the depth is bounded and predictable.
Plain recursive fib repeats the same sub-calls and takes exponential time. Storing each result the first time (for example with functools.lru_cache) means each value is computed once, so the time drops to linear.
In CPython, a list is implemented as a dynamic array of pointers to objects (a PyListObject). Elements are stored in contiguous memory addresses as pointers, allowing O(1) random access by index. When capacity is exceeded, CPython overallocates memory geometrically (approx 1.125x) to maintain amortized O(1) append operations.
a[10:20] not raise an IndexError on a list of length 5?Python index lookups (a[i]) raise an IndexError for out-of-range bounds, but slicing (a[start:stop]) is forgiving: Python clamps start and stop indices to the valid bounds (0 to len), returning an empty list [] without error.
a[i] and a[i:i+1]?a[i] returns the item at index i and raises IndexError if i is out of range. a[i:i+1] returns a new list with zero or one item and never raises an error for a range that is too big.
When elements are removed using del or .remove() during a loop, the list shifts left, causing the loop's internal index counter to skip elements or process incorrect items. To safely modify during iteration, iterate over a shallow copy (for item in list[:]:) or use a list comprehension.
list.pop(0)?Because list.pop(0) takes O(N) time, production systems use collections.deque (double-ended queue), which implements a doubly-linked list of blocks providing O(1) appends and pops from both ends.
Python passes a reference to the list, so the parameter is another name for the caller's list. Methods such as append mutate that shared list. To avoid it, work on items.copy() or return a new list.
In Python 3 (unlike Python 2), list comprehensions have their own function-like local scope. Variables defined inside the comprehension (e.g. x in [x for x in range(5)]) do not leak into or overwrite variables in the enclosing scope.
itertools.zip_longest() pairs iterables up to the longest sequence, filling missing values from shorter iterables with an optional fillvalue (default None).
Since Python 3.10, zip(a, b, strict=True) raises a ValueError when the lists have different lengths. Without it, the extra items of the longer list are silently ignored.
copy.deepcopy() handle circular references?copy.deepcopy() maintains an internal memo dictionary (memo={}) that maps original object IDs to their newly cloned copies. If an object is encountered that has already been cloned in the current traversal, it reuses the memoized reference, preventing infinite recursion.
A jagged list is a nested list whose rows have different lengths, such as the marks of classes with different student counts. Python allows it because each row is its own list, but row[col] on a short row can raise IndexError.
By rows: for row in matrix. By columns: for c in range(len(matrix[0])): for r in range(len(matrix)): matrix[r][c], or for column in zip(*matrix) for a compact version on a rectangular matrix.
set(nums) alone not always acceptable for removing duplicates in interview questions?While set() removes duplicates in O(N) time, sets do not guarantee insertion order preservation across all algorithms. When the interview problem requires preserving original element order, using dict.fromkeys() or a two-pointer pass is required.
Keep one index in each list. Compare the two front items, append the smaller one and move that index. When one list ends, append the rest of the other. Every item is touched once, so the time is O(n + m).
Lesson: Lists CheckpointA tuple promises that the group will not change. That is safer for fixed records such as a point or an order line, it tells the reader the intent, and a tuple can be used where only unchangeable values are allowed.
Lesson: What is a Tuple?Yes to both. A tuple fixes which objects it holds, not their contents. If it holds a list, the list can still be changed with append or item assignment.
t[0] = 1 on a tuple?Python raises a TypeError, because a tuple does not support item assignment.
Lesson: What is a Tuple?a, b = b, a swap two values without a temporary variable?Python first builds a tuple from the right side using the old values, (b, a). Then it unpacks that tuple into a and b. Nothing is overwritten before both values are safely stored in the tuple.
It collects all values not taken by the other names. The result is always a list, even if it holds one item or is empty, and a statement can have only one starred name.
Lesson: Unpacking and Swapping_ in name, _, age = record?It is a normal variable name. By convention it means 'this value is not used'. Python does not treat it in any special way.
Lesson: Unpacking and SwappingIt does not really. return a, b packs the values into one tuple, and the caller usually unpacks it: x, y = func(). The caller can also keep the tuple whole.
enumerate better than range(len(items))?enumerate gives the position and the item together, so the code is shorter and cannot go out of range. It also has a start argument.
Swap the order to (mark, name) and use sorted(pairs, reverse=True). The tuples are then compared by mark first, and by name when marks are equal.
When the group is a fixed record of a few values, such as a point or a customer row, and should not change. Use a list when the number or the content of items will change.
Lesson: Tuples Checkpointfirst, *rest = items do when items has only one item?first gets that item and rest is an empty list. A starred name always gets a list, and it can be empty.
t = ([1], 2); t[0].append(5) still work?The tuple holds a reference to a list and that reference is not replaced. The list is a mutable object, so it can change in place. Immutability covers only the tuple's own slots.
Lesson: Tuples CheckpointString immutability provides three major benefits: 1) Security and Safety: String references passed to network APIs, file handles, or system calls cannot be unexpectedly mutated by other threads; 2) Hashability: Because strings are immutable, their hash value is computed once and cached, enabling them to serve as dictionary keys and set elements; 3) Memory Optimization: Python can safely intern identical string literals to save memory.
Lesson: String Indexing and Slicings[::-1]?The time complexity is O(N) and space complexity is O(N), as Python allocates a new string object of length N and copies the characters in reverse order.
Lesson: String Indexing and Slicings[-1] preferred over s[len(s) - 1]?Both give the last character, but s[-1] is shorter, harder to get wrong, and works without calling len. Negative indexes are the Python way to count from the end.
.lower() and .casefold() in Python?.lower() performs standard ASCII and basic Unicode lowercase conversion. .casefold() is a more aggressive, caseless matching method designed for international Unicode text (e.g. converting German 'ß' to 'ss'). For robust caseless string comparison, PEP 8 recommends .casefold().
s.strip('xyz') do?When characters are passed to .strip('xyz'), it strips all leading and trailing occurrences of any character in the set {'x', 'y', 'z'}, continuing until a non-matching character is encountered.
strip() return when the string has only spaces?It returns an empty string "". This is why text.strip() is a good truthiness test: an empty result is falsy, so if not text.strip(): catches blank input.
isdigit(), isnumeric(), and isdecimal()?isdecimal() is the strictest: it checks for base-10 digits (0-9). isdigit() includes digits plus superscripts/subscripts like ², ³. isnumeric() is the broadest: it includes fractions (½, ¾) and Roman numerals in Unicode.
.startswith() and .find() in CPython?.startswith() runs in O(K) time where K is the length of the prefix. .find() uses a fast Boyer-Moore-Horspool / two-way string search algorithm in CPython, with average O(N + M) performance.
Each lesson has quizzes, practice and more interview questions on the same topic.
Open the first lesson