1. What happens if you modify a global variable inside a function without the global declaration?
Answer: If you assign to the name, Python creates a new local variable; if you reference it before assigning, you get UnboundLocalError.
Python decides a variable’s scope by where it’s assigned, not where it’s used. If a function contains any assignment to a name, that name is treated as local for the entire function — even lines before the assignment.
Two cases follow:
- Assignment with no prior reference:
x = 5inside the function creates a localx. The globalxis untouched. No error — but no global modification either. - Reference before assignment: if the function does
print(x)and laterx = 5, Python has already markedxas local. Theprintruns before the local exists, so it raisesUnboundLocalError— notNameError, even though a globalxexists.
To actually modify the global, you must declare global x at the top of the function. The interview answer: assignment makes it local (or raises UnboundLocalError when referenced before assignment); without global, the global stays untouched.
2. What is the time complexity of appending an element to a Python list?
Answer: O(1) amortized.
A Python list is a dynamic array — a contiguous block of memory that grows by overallocation. When you call append, there’s usually spare capacity, so the element is written at the end in constant time.
When the array fills up, Python allocates a larger block (roughly 1.125× the old size) and copies every existing element into it — that single append is O(n). But such resizes are rare: they happen only when capacity is exhausted, and the exponential growth means the average cost over many appends stays O(1).
That’s what “amortized O(1)” means: the occasional expensive copy is spread across all the cheap appends, so n appends cost O(n) total.
The interview answer: amortized O(1). A plain append is fast; infrequent resizes absorb the copying cost.
3. What will print({1, 2} < {1, 2, 3}) return?
Output: True
On sets, the comparison operators take set-theoretic meanings. < tests proper subset: A < B is true when every element of A is in B and A is strictly smaller.
Here {1, 2} is a proper subset of {1, 2, 3} — both 1 and 2 are present in the larger set, and the sets are not equal. So the result is True.
The family of operators:
A < B— proper subset (A strictly contained).A <= B— subset (contained or equal).A > B— proper superset.A >= B— superset.
Note these aren’t element-wise comparisons like on lists — they’re pure subset relations. The interview answer: True, because {1, 2} is a proper subset of {1, 2, 3}.
4. What is the default return value of a Python function that executes without an explicit return statement?
Answer: None.
If a function’s body runs to the end without hitting a return (or a return with no value), Python implicitly returns None.
def f():
x = 1
print(f()) # None
Every Python function returns something — there’s no “void” concept like in C/Java. Functions that conceptually return nothing actually return the None object. This is why checking the result of a mutating method (list.append returns None) is a classic bug: the value is silently discarded.
The interview answer: None — implicit, always.
5. What is the output of print(“Python”.find(“z”))?
Output: -1
str.find(sub) searches for a substring and returns the index of the first occurrence. When the substring isn’t found at all, it returns -1.
There’s no substring "z" in "Python", so find returns -1. Note it does not raise — that’s the deliberate contrast with str.index(), which does the same search but raises ValueError when the substring is missing.
The rule of thumb: use find when a missing match is a normal possibility (you’ll check for -1); use index when a missing match is an error you want to surface. The interview answer: -1.
6. How do you define a docstring in Python?
Answer: A string literal — conventionally triple-quoted — placed as the first statement of a module, function, class, or method.
A docstring is documentation attached to a code object. It must be the very first statement in the body (before any code), and it’s automatically stored in the object’s __doc__ attribute, which tools like help() read.
def add(a, b):
"""Return the sum of a and b."""
return a + b
print(add.__doc__) # "Return the sum of a and b."
The convention is triple quotes, """...""", because docstrings are often multi-line. The distinguishing feature: it’s an expression statement that Python captures as metadata rather than discarding.
The interview answer: a string literal as the first statement of a module/function/class, accessible via __doc__.
7. What will print(type(lambda: None)) output?
Output: <class 'function'>
A lambda is just a compact way to define a function. It creates the exact same underlying type as a def — the built-in function type. There is no separate lambda type.
lambda: None defines an anonymous function taking no arguments and returning None. Its type is function.
There’s no class 'lambda' — that’s a common misconception. The only difference between lambda and def is syntax: lambda is restricted to a single expression and has no name. Both produce function objects. The interview answer: <class 'function'>.
8. What is the output of the following list operation?
lst = [1, 2, 3]
lst.extend("45")
print(lst)
Output: [1, 2, 3, '4', '5']
extend iterates over its argument and appends each element individually. It doesn’t add the argument as a single item.
"45" is a string, and iterating a string yields its characters. So extend("45") appends '4' and then '5' — two separate one-character strings.
The contrast is append: lst.append("45") would add the whole string as a single element, giving [1, 2, 3, '45'].
The distinction in one line: append adds one object; extend adds every element of an iterable. Since a string is iterable, extend unpacks its characters. Result: [1, 2, 3, '4', '5'].
9. What is the purpose of init.py in Python package directories?
Answer: It marks the directory as a Python package, and its code runs automatically when the package is imported.
Before Python 3.3, __init__.py was required for a directory to be importable as a package — without it, import mypackage simply failed. With implicit namespace packages (3.3+), it’s optional for basic importing, but it’s still standard practice.
Two jobs it does:
- Marks the package — signals to the import system that this directory holds a package.
- Initialization — its contents execute once when the package is first imported. You can put package-level imports,
__all__declarations, or setup code there, making the package’s public API explicit.
The interview answer: __init__.py designates a directory as a package and runs its initialization code on import.
10. What will print(math.trunc(-2.8)) return?
Output: -2
math.trunc(x) removes the fractional part, keeping only the integer part — it truncates toward zero.
For -2.8, truncation toward zero means dropping the -0.8 and keeping -2. The result is the integer -2.
This is subtly different from floor division and int():
math.trunc(-2.8)=-2(toward zero).math.floor(-2.8)=-3(toward negative infinity).math.ceil(-2.8)=-2(toward positive infinity).
The trap is assuming “truncate” means “round down.” It doesn’t — for negatives, truncation and flooring diverge. The interview answer: -2.
11. What is the primary difference between range() in Python 3 and xrange() in Python 2?
Answer: In Python 3, xrange() is gone, and range() became the memory-efficient sequence object — effectively what xrange() was in Python 2.
Python 2 had two functions:
range(n)— eagerly built a list in memory. Fine for small n, wasteful for huge n.xrange(n)— a lazy sequence object that generated values on demand without materializing the whole list.
Python 3 eliminated the duplication: xrange was removed, and range was redefined to be the lazy, memory-efficient object. So in Python 3, range(10**9) uses a constant amount of memory regardless of size.
Note that Python 3’s range is still a sequence — you can index it (r[5]), slice it, and check membership efficiently — it just isn’t a list. The interview answer: Python 3’s range is the lazy, sequence-like object that Python 2 called xrange.
12. What will print(isinstance(lambda x: x, object)) output?
Output: True
The foundational claim of Python’s object model: everything is an object — functions, classes, modules, lambdas, even type itself.
A lambda is a function object, and every object in Python is an instance of object (either directly or through its class hierarchy). So isinstance(lambda x: x, object) is True.
This is worth connecting to the other meta-questions: a lambda is an instance of function, function is an instance of type, and all of them are instances of object. There’s nothing in Python that isn’t an object. The interview answer: True.
13. Which statement regarding try…except…else is correct?
Answer: The else block runs only when no exception was raised in the try block.
The full try statement has up to four parts:
try— the guarded code.except— runs if a matching exception was raised.else— runs only if the try block completed without any exception.finally— always runs, exception or not.
The subtlety: else is distinct from finally. finally runs unconditionally; else runs only on the clean path. The typical use of else is to hold code that must run only when nothing went wrong — for example, committing a transaction only after the risky work succeeded.
The interview answer: else runs exclusively when no exception occurred in try.
14. What will print(str.upper(“hello”)) return?
Output: "HELLO"
Methods in Python are just functions attached to a class. str.upper is the function; when called on an instance, "hello".upper(), Python implicitly passes the instance as the first argument.
But you can also call the underlying function directly with the instance passed explicitly: str.upper("hello"). The string "hello" is provided as self, and the method uppercases it, returning "HELLO".
This is called an unbound method call (in Python 3 terms, just calling the function attribute of the class). It’s equivalent to the bound form — useful in map(str.upper, strings) and similar contexts.
The interview answer: "HELLO" — unbound method calls accept the instance explicitly as the first argument.
15. What is the output of the following dictionary unpacking code?
d1 = {'a': 1}
d2 = {'a': 2, 'b': 3}
merged = {**d1, **d2}
print(merged)
Output: {'a': 2, 'b': 3}
The ** unpacking operator (Python 3.5+) splats dictionaries into a literal. When keys collide, later definitions win — the merge processes d1 first, then d2 overwrites.
- From
**d1:'a': 1. - From
**d2:'a': 2overwrites the1;'b': 3is added.
Result: {'a': 2, 'b': 3}.
This is the modern, concise way to merge dicts (pre-3.9 it was {**a, **b}; 3.9+ also has a | b). The interview point is the overwrite rule: rightmost wins on overlapping keys. The values are not combined — 'a' is 2, not [1, 2].
16. What does the pass keyword accomplish inside a class definition?
Answer: It’s a syntactic placeholder for an empty body.
A class statement requires an indented block after it. If you want a class with no methods or attributes yet — a stub — you need something in the block, and pass fills it while doing nothing.
class Stub:
pass
Execution continues normally; the class is created empty. This is common during incremental design, or to define an exception class that needs no added behavior: class MyError(Exception): pass.
pass is purely a no-op statement — it consumes a syntactic slot and performs zero actions. The interview answer: a placeholder so an empty class body is syntactically valid.
17. What is the output of print(5 // 2) and print(-5 // 2)?
Output: 2 and -3
Floor division (//) rounds down toward negative infinity, not toward zero — that distinction is the entire question.
5 // 2:5 / 2 = 2.5, floored →2.-5 // 2:-5 / 2 = -2.5, floored → the next integer below-2.5, which is-3.
The result for the negative case surprises people who expect truncation (which would give -2). Floor division never rounds up: it always goes to the greatest integer less than or equal to the exact quotient.
This is why // pairs with the % modulo operator such that a == (a // b) * b + (a % b) holds even for negatives. The interview answer: 2 and -3.
18. What method turns a string of items separated by commas into a list?
Answer: str.split(",").
split(delimiter) divides a string around every occurrence of the delimiter and returns a list of the pieces.
"apple,banana,cherry".split(",") # ['apple', 'banana', 'cherry']
The default — split() with no argument — splits on runs of whitespace, which is handy for whitespace-separated input. With a specific delimiter, split is the standard way to parse CSV-ish strings, key/value pairs, and similar formats.
The related operation goes the other way: ",".join(list_of_strings) builds a string from a list. The interview answer: str.split(",").
19. Which function returns True if all elements of an iterable evaluate to truthy?
Answer: all().
all(iterable) returns True only when every element is truthy. As a special case, it returns True for an empty iterable (vacuous truth — nothing contradicts the claim).
all([1, 2, 3]) # True
all([1, 0, 3]) # False — 0 is falsy
all([]) # True
The complement is any(), which returns True if at least one element is truthy (and False for empty). Both short-circuit: all stops at the first falsy element, any stops at the first truthy one.
The interview answer: all(), with any() as the existential counterpart.
20. What will print(type(10 / 2)) output in Python 3?
Output: <class 'float'>
The / operator is true division in Python 3: it always produces a float, even when the division comes out exactly.
10 / 2 is 5.0 — a float — so type reports <class 'float'>.
This was a deliberate change from Python 2, where / performed integer division on integers (10 / 2 was 2, an int). In Python 3:
/— always float (true division).//— floor division; returns anintwhen both operands are ints.
The interview answer: <class 'float'> — Python 3’s / always yields a float.
Premium Content
Unlock Top 50 - Part 3 and all premium lessons with a subscription.
From ₹199.99/year — See plans