1. Is Python compiled, interpreted, or both?
Difficulty: EasyType: MCQTopic: Execution Model
- Only compiled language
- Only interpreted language
- Both compiled and interpreted
- Neither compiled nor interpreted
Python performs both compilation and interpretation. When you run a Python file, the source code is first compiled into bytecode, a lower-level form that the Python Virtual Machine understands. This compiled bytecode is stored in dot py c files.
After that, the Python Virtual Machine interprets the bytecode line by line at runtime. This is why Python is considered interpreted in practice. Some implementations such as PyPy use Just-In-Time compilation to further speed up execution by compiling bytecode to native machine code at runtime.
Correct Answer: Both compiled and interpreted
Example Code
python myfile.py
2. Which of these joins two lists to make one new list?
Difficulty: EasyType: MCQTopic: List Ops
- append()
- + operator
- join()
- merge()
The plus operator creates a new list by combining two existing lists. It does not modify the originals but returns a fresh list containing all elements from both.
If you use the extend method instead, it adds items of the second list into the first list in place. append adds the entire list as a single element, which is different from concatenation.
Correct Answer: + operator
Example Code
a=[1,2,3]; b=[4,5]; res=a+b
3. Which loop runs until a condition becomes false?
Difficulty: EasyType: MCQTopic: Control Flow
- for loop
- while loop
- do while loop
- foreach loop
A while loop in Python continues executing its block as long as the given condition evaluates to true. Once it becomes false, the loop stops automatically.
This makes while loops useful when you do not know in advance how many times the block should repeat, such as reading data until an end condition is met.
Correct Answer: while loop
Example Code
i=0
while i<5:
print(i); i+=1
4. Which method gives the floor value of a float?
Difficulty: EasyType: MCQTopic: Math Basics
- ceil()
- floor()
- round()
- truncate()
The floor function from Python’s math module returns the greatest integer less than or equal to a number. It simply drops the fractional part and rounds down.
For example, the floor of three point seven is three, while the ceil function would give four. It is commonly used when converting float values to integer boundaries in numeric programs.
Correct Answer: floor()
Example Code
import math
math.floor(3.7)
5. In Python, what is the difference between '/' and '//'?
Difficulty: EasyType: MCQTopic: Math Basics
- Both are same
- / gives float, // gives integer
- // gives float, / gives integer
- Both give remainder
The single slash performs true division and always returns a floating-point result, even when both operands are integers.
The double slash performs floor division. It divides the numbers and rounds the result down to the nearest integer. This is often used when integer results are required, such as index calculations.
Correct Answer: / gives float, // gives integer
Example Code
print(5/2, 5//2)
6. Is indentation required in Python?
Difficulty: EasyType: MCQTopic: Python Syntax
- Optional
- Only for comments
- Yes required
- Used only in functions
Indentation is mandatory in Python because it defines the structure of code blocks. Instead of braces or keywords, Python uses indentation level to determine which statements belong together.
This rule improves readability but also enforces strict consistency. Incorrect indentation leads to an Indentation Error during execution.
Correct Answer: Yes required
Example Code
if True:
print('Yes')7. Can a function be passed as an argument in Python?
Difficulty: MediumType: MCQTopic: Function Args
- No
- Only built-in
- Yes, any function
- Only lambda
Functions in Python are first-class objects. This means they can be stored in variables, returned from other functions, or passed as arguments just like numbers or strings.
This feature enables higher-order functions, where one function operates on another, such as custom sort keys or functional programming patterns like map and filter.
Correct Answer: Yes, any function
Example Code
def add(x,y): return x+y
def apply(f,a,b): return f(a,b)
apply(add,3,5)
8. Why is Python called dynamically typed?
Difficulty: MediumType: MCQTopic: Python Basics
- Type must be declared
- Type fixed at compile time
- Type decided at runtime
- Type checking not done
Python decides the data type of a variable at runtime based on the value you assign. You do not need to declare a type explicitly.
This flexibility makes coding faster but can cause runtime errors if unexpected types are passed. It contrasts with statically typed languages where type checks happen at compile time.
Correct Answer: Type decided at runtime
9. What does the 'pass' statement do?
Difficulty: EasyType: MCQTopic: Control Flow
- Ends program
- Skips current iteration
- Does nothing
- Raises an error
The pass statement is a placeholder that performs no action. It is used where syntactically a statement is required but no operation is needed yet.
Developers often use it while writing skeleton code for functions, loops, or classes, to avoid syntax errors before logic is implemented.
Correct Answer: Does nothing
Example Code
def temp():
pass
10. Explain how arguments are passed in Python. Is it by value or by reference?
Difficulty: MediumType: SubjectiveTopic: Function Args
Python follows a model known as pass by object reference. This means the reference to the object is passed to the function, not the actual value or a copy.
If the object is mutable, like a list or dictionary, changes made inside the function affect the original object. For immutable objects such as integers or strings, a new object is created instead, giving the impression of pass by value.
Understanding this helps prevent bugs when modifying arguments within functions and explains why behavior differs between lists and numbers.
Example Code
def f(x): x.append(1)
a=[0]; f(a); print(a)
11. What is a lambda function in Python?
Difficulty: EasyType: SubjectiveTopic: Lambda Func
A lambda function is an anonymous, one-line function defined using the keyword lambda. It can take any number of inputs but only one expression, which is automatically returned.
They are used when a short function is needed temporarily, for example as a key in sorting or inside functions like map and filter. Though concise, excessive use of lambda can hurt readability compared to normal function definitions.
Example Code
square = lambda x: x*x
print(square(4))
12. Explain list comprehension with an example.
Difficulty: EasyType: SubjectiveTopic: Comprehensions
List comprehension is a compact way to create a new list by applying an expression to each element of an iterable. It replaces long loops with a single, readable line.
For instance, to build a list of squares you can write square equals x star star 2 for each x in a list. It is both faster and cleaner than using append inside a loop and is widely used in data transformation tasks.
Example Code
a=[1,2,3]
squares=[x*x for x in a]
13. What are *args and **kwargs used for?
Difficulty: MediumType: SubjectiveTopic: Function Args
The *args parameter allows a function to receive any number of positional arguments as a tuple. It provides flexibility when you don’t know how many inputs will be passed.
The **kwargs parameter collects keyword arguments into a dictionary, letting you handle named parameters dynamically. Together they make Python functions highly extensible and are often used in wrappers and decorators.
Example Code
def demo(*a, **b): print(a,b)
14. Explain break, continue, and pass with examples.
Difficulty: MediumType: SubjectiveTopic: Control Flow
The break statement immediately exits the loop, skipping any remaining iterations. continue jumps to the next iteration, skipping only the current one. pass simply does nothing and is used as a placeholder.
These three help control the flow of loops. break is often used when a condition is met, continue is used to skip unwanted cases, and pass keeps structure valid while leaving logic for later.
Example Code
for i in range(5):
if i==2: continue
if i==4: break
print(i)
15. Differentiate between a Set and a Dictionary in Python.
Difficulty: MediumType: SubjectiveTopic: Collections
A set is an unordered collection of unique values, while a dictionary is an unordered collection of key-value pairs. Sets are useful for membership testing and removing duplicates.
Dictionaries map keys to values for quick look-up and modification. Both are built on hash tables internally, but a set stores only keys, whereas a dictionary stores both keys and values.
Example Code
myset={1,2,3}
mydict={'a':1,'b':2}16. Which statement best describes lists and tuples in Python?
Difficulty: EasyType: MCQTopic: Lists Tuples
- Both are mutable and ordered
- List is mutable; tuple is immutable
- List is unordered; tuple is ordered
- Both are immutable and unordered
A list lets you add, remove, or replace items; a tuple does not. That is why lists are great for dynamic collections while tuples are good for fixed records.
Because tuples are immutable, they can be used as dictionary keys when they contain only hashable values. This property makes tuples a safe choice for representing coordinates or composite keys.
Correct Answer: List is mutable; tuple is immutable
Example Code
t = (1,2,3)
# t[0] = 9 # TypeError
lst = [1,2,3]; lst[0] = 9
17. From Python 3.7 onward, what is guaranteed about dict iteration?
Difficulty: MediumType: MCQTopic: Dict Basics
- Random order every run
- Sorted by key
- Insertion order preserved
- Sorted by value
Dictionaries preserve insertion order as a language guarantee from Python 3.7 onward. Keys come out in the same order you inserted them.
This makes dicts suitable for tasks where relative order matters without needing OrderedDict for most cases.
Correct Answer: Insertion order preserved
Example Code
d = {}
d['a']=1; d['c']=3; d['b']=2
print(list(d.keys())) # ['a','c','b']18. What is the average time complexity to test membership in a Python set?
Difficulty: EasyType: MCQTopic: Set Ops
- O(1) average
- O(log n)
- O(n)
- O(n log n)
Sets are hash tables, so membership tests are constant time on average. Lists are linear time because they scan elements.
Use sets for fast membership checks, deduplication, and set operations like union and intersection.
Correct Answer: O(1) average
Example Code
s = {1,2,3}
print(2 in s) # fast19. Which expression builds a list of squares from nums?
Difficulty: EasyType: MCQTopic: Comprehensions
- [x*x for x in nums]
- x*x in nums
- map(x*x, nums)
- {x*x for x in nums}
A list comprehension in square brackets returns a list. A set comprehension uses curly braces and returns a set. map needs a function object, not an expression.
Choose list comprehension when order and duplicates matter and you need a list API afterward.
Correct Answer: [x*x for x in nums]
Example Code
nums=[1,2,2,3]
sq=[x*x for x in nums] # [1,4,4,9]
20. Which option fully copies a nested list so inner lists are independent?
Difficulty: MediumType: MCQTopic: Copy Semantics
- new = old
- new = old[:]
- import copy; new = copy.copy(old)
- import copy; new = copy.deepcopy(old)
A shallow copy duplicates the outer list but keeps references to the same inner lists. deep copy recursively copies nested structures so edits do not leak back.
Use deepcopy when structures are nested and you truly want independence between old and new.
Correct Answer: import copy; new = copy.deepcopy(old)
Example Code
import copy
old=[[1],[2]]
new=copy.deepcopy(old)
new[0][0]=9 # old unchanged
21. In Python 3.9+, which merges two dicts into a new one?
Difficulty: MediumType: MCQTopic: Dict Ops
- d1 + d2
- d1 | d2
- merge(d1,d2)
- dict.merge(d1,d2)
The vertical bar operator returns a new dict with keys from both, where right side wins on conflicts. The update method mutates in place instead.
This operator makes merges expressive and avoids accidental mutation of the original mapping.
Correct Answer: d1 | d2
Example Code
a={'x':1}
b={'y':2,'x':9}
c=a|b # {'x':9,'y':2}22. Which structure gives O(1) appends and pops at both ends for queues?
Difficulty: MediumType: MCQTopic: Queue Stack
- list
- set
- collections.deque
- array.array
A deque is a double-ended queue optimized for fast appends and pops at both left and right. Lists are fast at the right end but slow for pops from the left.
Choose deque for FIFO queues, sliding windows, and bounded buffers.
Correct Answer: collections.deque
Example Code
from collections import deque
q=deque()
q.append(1); q.append(2); q.popleft()
23. Which module implements a min-heap based priority queue?
Difficulty: MediumType: MCQTopic: Heap Queue
- bisect
- heapq
- queue
- functools
heapq maintains the smallest element at index zero and supports push, pop, and nlargest or nsmallest helpers.
It is lightweight and works on plain Python lists, making it ideal for scheduling, streaming top-k, and Dijkstra style tasks.
Correct Answer: heapq
Example Code
import heapq
h=[]
heapq.heappush(h,3); heapq.heappush(h,1)
print(heapq.heappop(h)) # 1
24. How do you get the top 3 most frequent items using Counter?
Difficulty: EasyType: MCQTopic: Counter Tool
- c.top3()
- c.max(3)
- c.most_common(3)
- c.head(3)
Counter counts hashable items and most_common returns items sorted by frequency. It is concise for word counts and event tallies.
You can also add, subtract, and combine counters when merging tallies from multiple sources.
Correct Answer: c.most_common(3)
Example Code
from collections import Counter
c=Counter('banana')
print(c.most_common(3))25. When should you choose list, tuple, set, or dict? Explain with trade-offs.
Difficulty: MediumType: SubjectiveTopic: Data Structures
Use a list for ordered, indexable collections that change over time; appends are fast and you keep duplicates. Choose a tuple for fixed records that must not change; immutability improves safety and allows use as dictionary keys when elements are hashable.
Pick a set when you need uniqueness and fast membership tests; set operations like union and intersection are expressive and efficient. Use a dict when you must map keys to values with O(1) average lookup; it is the most common structure for labelled data and configuration.
Think about mutability, need for keys, and performance. For example, membership tests in a set or dict are O(1) average, while in a list they are O(n).
Example Code
users=['a','b','b']
unique=set(users)
points={('lat','lon'): (30.1,78.0)}26. Explain shallow copy versus deep copy for nested structures with an example.
Difficulty: MediumType: SubjectiveTopic: Copy Semantics
A shallow copy duplicates the outer container but shares references to inner objects. Changing an inner list in the copy also changes it in the original, because both point to the same inner object.
A deep copy recursively copies inner objects, so edits in the copy do not affect the original. Use shallow copy when inner items are immutable or you want sharing; use deep copy when you need isolation between complex nested graphs.
Example Code
import copy
orig=[[1],[2]]
sh = copy.copy(orig)
dp = copy.deepcopy(orig)
sh[0][0]=9 # orig changes
# dp edits do not affect orig
27. How do you implement a stack and a queue in Python? Discuss complexity and when to use each.
Difficulty: MediumType: SubjectiveTopic: Queue Stack
A stack uses list append and pop for LIFO; both are O(1) average at the right end. It is ideal for undo, parsing, and depth-first search where last-in comes out first.
A queue should use collections.deque for FIFO; append at the right and popleft at the left are O(1). Lists are inefficient for popleft because shifting is O(n). Use queues for breadth-first search, task scheduling, and producer consumer pipelines.
Example Code
# stack
stack=[]; stack.append(1); stack.append(2); stack.pop()
# queuerom collections import deque
q=deque([1,2]); q.append(3); q.popleft()
28. Explain dictionary comprehension and one practical use case.
Difficulty: EasyType: SubjectiveTopic: Comprehensions
Dictionary comprehension builds a mapping in a single readable expression. It iterates a source and computes key and value pairs on the fly.
A common use is transforming a list into a lookup table or filtering an existing dict by a condition. It keeps code concise and avoids multi-step loops and temporary variables.
Example Code
names=['a','bb','ccc']
lengths={n:len(n) for n in names if len(n)>1}29. What is defaultdict? When is it helpful and what pitfalls should you avoid?
Difficulty: MediumType: SubjectiveTopic: Dict Ops
defaultdict creates missing keys automatically using a factory like list or int, so you can append or add without pre-checks. It reduces boilerplate when grouping items or counting events.
A pitfall is accidental key creation when reading; any access can materialize a key. Convert to a plain dict before serialization if you do not want the default behavior to surprise downstream code.
Example Code
from collections import defaultdict
groups=defaultdict(list)
for k,v in [('a',1),('a',2)]:
groups[k].append(v)
print(dict(groups))30. What is the safest way to handle a list default argument in a function?
Difficulty: MediumType: MCQTopic: Function Args
- Use def f(x, arr=[]):
- Use def f(x, arr=list()):
- Use def f(x, arr=None): then set arr = [] inside
- Rely on *args instead of defaults
Default arguments are evaluated once at function definition time. A mutable default like a list is shared across calls and can cause subtle bugs.
Using None as the default and creating a new list inside ensures each call gets a fresh object. This is the standard safe pattern interviewers expect.
Correct Answer: Use def f(x, arr=None): then set arr = [] inside
Example Code
def add_item(x, arr=None):
if arr is None:
arr = []
arr.append(x)
return arr31. In Python name resolution follows which order?
Difficulty: EasyType: MCQTopic: Scope LEGB
- Global, Local, Builtins, Enclosing
- Local, Enclosing, Global, Builtins
- Builtins, Global, Enclosing, Local
- Enclosing, Local, Global, Builtins
Python searches first in the Local scope, then in any Enclosing function scopes, then in the module Global scope, and finally in Builtins.
Knowing LEGB helps explain behaviors with closures, nonlocal, and why a name resolves the way it does.
Correct Answer: Local, Enclosing, Global, Builtins
Example Code
x='G'
def outer():
x='E'
def inner():
x='L'
return x
return inner()
print(outer())32. What is the primary purpose of a decorator in Python?
Difficulty: MediumType: MCQTopic: Decorators
- Compile code to bytecode faster
- Modify or extend a function's behavior without changing its source
- Convert a function into a class
- Force type checking at runtime
Decorators are higher-order callables that take a function and return a wrapped function. They enable cross-cutting concerns like logging, timing, caching, and access control.
This keeps core logic clean while applying consistent policies in a reusable way.
Correct Answer: Modify or extend a function's behavior without changing its source
Example Code
def log(fn):
def wrap(*a, **k):
print('call', fn.__name__)
return fn(*a, **k)
return wrap
@log
def add(x,y): return x+y33. Which statement is correct about instance, class, and static methods?
Difficulty: EasyType: MCQTopic: Method Types
- Instance methods receive cls; class methods receive self
- Static methods receive self automatically
- Instance methods receive self; class methods receive cls; static methods receive neither
- All three receive self
An instance method operates on object state via self. A class method operates on class state via cls. A static method is just a namespaced function.
Choosing the right kind clarifies intent and avoids accidental coupling to instance or class state.
Correct Answer: Instance methods receive self; class methods receive cls; static methods receive neither
Example Code
class A:
def m(self): return 'inst'
@classmethod
def c(cls): return 'class'
@staticmethod
def s(): return 'static'34. How does Python decide which method to call in multiple inheritance?
Difficulty: MediumType: MCQTopic: Inheritance MRO
- Left-to-right depth-first order without merges
- Randomized order each run
- C3 linearization (Method Resolution Order)
- Always the most derived class only
Python computes a single consistent linear order called the MRO using C3 linearization. It respects subclass precedence and the order of bases.
You can inspect it via Class.__mro__ to understand how super will resolve methods.
Correct Answer: C3 linearization (Method Resolution Order)
Example Code
class A: pass
class B(A): pass
class C(B, A): pass
print(C.__mro__)
35. What is the usual difference between __str__ and __repr__?
Difficulty: EasyType: MCQTopic: Magic Methods
- __str__ is for developers; __repr__ is for end users
- __repr__ is unambiguous; __str__ is readable
- Both must return bytes
- Both are called by print
__repr__ should ideally return a string that could recreate the object or be precise for debugging. __str__ returns a user-friendly display.
If only __repr__ is defined, print will fall back to it. Good object printing improves debuggability.
Correct Answer: __repr__ is unambiguous; __str__ is readable
Example Code
class P:
def __init__(self,x): self.x=x
def __repr__(self): return f'P(x={self.x})'
def __str__(self): return f'Point {self.x}'36. Why use @property for an attribute?
Difficulty: MediumType: MCQTopic: Properties
- To make the attribute public only
- To remove the need for __init__
- To expose an attribute-like API while running getter or setter logic
- To auto-generate database columns
The property decorator lets you compute or validate values while keeping a simple attribute syntax. You can add caching, type checks, or invariants.
It preserves backward compatibility if a previously simple field later needs logic.
Correct Answer: To expose an attribute-like API while running getter or setter logic
Example Code
class User:
def __init__(self,name): self._name=name
@property
def name(self): return self._name.title()
@name.setter
def name(self,v): self._name=v.strip()37. What do dataclasses primarily help with?
Difficulty: MediumType: MCQTopic: Dataclasses
- Async IO performance
- Auto-generating boilerplate like __init__, __repr__, and comparisons
- Replacing inheritance
- Dynamic typing at runtime
Dataclasses reduce boilerplate for simple data containers. They generate init, repr, eq, and more based on field definitions.
They keep code readable while supporting defaults, type hints, and post-init hooks.
Correct Answer: Auto-generating boilerplate like __init__, __repr__, and comparisons
Example Code
from dataclasses import dataclass
@dataclass
class Point:
x:int
y:int38. What is a key effect of defining __slots__ in a class?
Difficulty: HardType: MCQTopic: Slots
- Instances become immutable
- Methods run faster automatically
- Memory usage per instance can drop by removing __dict__
- Class cannot inherit from any base
__slots__ defines a fixed set of attributes and usually removes the instance dictionary. This lowers memory and can slightly speed attribute access.
It trades flexibility: you cannot add new attributes not declared in slots unless you keep a dict too.
Correct Answer: Memory usage per instance can drop by removing __dict__
Example Code
class Light:
__slots__ = ('on',)
def __init__(self): self.on=False39. Explain closures in Python and when to use nonlocal.
Difficulty: MediumType: SubjectiveTopic: Closures Scope
A closure is a function that captures variables from its enclosing scope, keeping them alive even after the outer function has returned. This enables function factories and configurable behaviors without global state.
Use nonlocal when the inner function needs to rebind a variable from the nearest enclosing scope rather than create a new local variable. This makes stateful callbacks and accumulators straightforward while avoiding mutable defaults or classes for small tasks.
Example Code
def counter():
count=0
def inc():
nonlocal count
count += 1
return count
return inc
c = counter(); c(); c()40. Show how to write a decorator that times a function and explain concerns.
Difficulty: MediumType: SubjectiveTopic: Decorators
Wrap the target function, record start and end times, and return the original result. Preserve metadata like name and docstring using functools.wraps so debugging and help stay meaningful.
Be careful with arguments and return values, and consider thread safety or async functions. For heavy production use, prefer proven libraries or add configuration toggles to disable timing in hot paths.
Example Code
import time, functools
def timed(fn):
@functools.wraps(fn)
def wrap(*a, **k):
t0=time.perf_counter()
try:
return fn(*a, **k)
finally:
dt=time.perf_counter()-t0
print(fn.__name__, 'took', dt)
return wrap41. What are Abstract Base Classes (ABC) and when would you use them?
Difficulty: MediumType: SubjectiveTopic: Abstract Base
An Abstract Base Class defines a formal interface by declaring abstract methods that subclasses must implement. It prevents instantiation until required methods are provided and communicates intent clearly to readers and tools.
Use ABCs when many implementations must share a contract, such as storage backends or payment gateways. They improve consistency, enable static checks, and reduce runtime surprises.
Example Code
from abc import ABC, abstractmethod
class Store(ABC):
@abstractmethod
def save(self, item): ...
class DB(Store):
def save(self, item): pass42. Explain class variables versus instance variables and a common pitfall.
Difficulty: MediumType: SubjectiveTopic: Class Variables
A class variable is shared by all instances; an instance variable belongs to one object. Reading a missing instance attribute falls back to the class attribute via normal lookup.
A common pitfall is using a mutable class variable like a list and then mutating it through one instance, which affects all others. Prefer per-instance fields for mutable state by assigning in __init__.
Example Code
class Bag:
items=[] # shared
def __init__(self):
self.things=[] # per instance
b1=Bag(); b2=Bag()
b1.things.append(1)
Bag.items.append('X')43. When would you prefer composition over inheritance in Python?
Difficulty: MediumType: SubjectiveTopic: OOP Basics
Choose composition when you want to build complex behavior by combining smaller parts, and the relationship is “has a” rather than “is a”. This avoids deep hierarchies and the fragility that comes from tight coupling to a base class.
Composition makes testing easier and lets you swap components without changing public APIs. Inheritance is still useful for true specialization, but composition keeps designs flexible and easier to evolve.
Example Code
class Engine:
def start(self): return 'start'
class Car:
def __init__(self, engine): self.engine=engine
def go(self): return self.engine.start()44. Which file mode creates a new file and raises an error if the file already exists?
Difficulty: EasyType: MCQTopic: File Handling
Mode x is exclusive creation. It succeeds only if the file does not exist. This is useful when you want to avoid accidental overwrites.
Mode w truncates existing files. Mode a appends if the file exists, or creates it if not. Mode r+ reads and writes without truncation.
Correct Answer: x
Example Code
with open('report.txt', 'x', encoding='utf-8') as f:
f.write('first run only')45. What is the main benefit of using 'with open(...) as f'?
Difficulty: EasyType: MCQTopic: Context Managers
- It speeds up disk I O by default
- It auto-closes the file even if an exception occurs
- It locks the file across processes
- It converts text to UTF eight automatically
The with statement uses a context manager. It ensures setup and teardown happen reliably. The file handle is closed even when exceptions are raised.
This reduces resource leaks and keeps code short compared to try and finally patterns.
Correct Answer: It auto-closes the file even if an exception occurs
Example Code
with open('data.txt', 'r', encoding='utf-8') as f:
for line in f:
process(line)46. What is the memory-friendly way to read a large text file?
Difficulty: MediumType: MCQTopic: File Handling
- f.read()
- f.readlines()
- Iterate line by line: for line in f
- Load into list then iterate
Iterating over the file object streams one line at a time. Memory stays small even for gigabyte files.
Calling read or readlines pulls the entire content or many lines into memory and can cause spikes.
Correct Answer: Iterate line by line: for line in f
Example Code
with open('big.log','r',encoding='utf-8') as f:
for line in f:
handle(line)47. What is the recommended way to avoid encoding issues when opening text files?
Difficulty: EasyType: MCQTopic: Encoding IO
- Rely on OS default encoding
- Always use binary mode
- Explicitly set encoding='utf-8'
- Disable buffering
Defaults vary by system and locale. Declaring UTF eight makes behavior predictable across machines and containers.
It also avoids subtle bugs with special characters, especially when moving code between Windows, Mac, and Linux.
Correct Answer: Explicitly set encoding='utf-8'
Example Code
with open('notes.txt','r',encoding='utf-8') as f:
txt=f.read()48. On Windows, how should you open a CSV to avoid extra blank lines when writing?
Difficulty: MediumType: MCQTopic: CSV Handling
- open('out.csv','w')
- open('out.csv','wb')
- open('out.csv','w', newline='')
- open('out.csv','a', encoding='ascii')
The csv module expects newline empty string to manage newlines correctly on Windows. Without it, you can get extra blank rows.
This setting lets the csv writer handle line endings consistently across platforms.
Correct Answer: open('out.csv','w', newline='')
Example Code
import csv
with open('out.csv','w',newline='',encoding='utf-8') as f:
w=csv.writer(f)
w.writerow(['id','name'])49. Which format is language-neutral and safer for untrusted data?
Difficulty: MediumType: MCQTopic: JSON Pickle
- pickle
- marshal
- json
- eval-based text
JSON is a text format supported across many languages and is safe to load from untrusted sources when using standard parsers.
pickle can execute arbitrary code during loading. Never unpickle data you do not trust.
Correct Answer: json
Example Code
import json
with open('cfg.json') as f:
cfg=json.load(f)50. Which statement correctly performs a relative import from the same package?
Difficulty: MediumType: MCQTopic: Imports Packages
- import utils.helper
- from .utils import helper
- from utils import .helper
- from .. import utils.helper
A single dot means the current package. This form keeps package internals flexible if you reorganize directories.
Absolute imports are clearer for public APIs. Relative imports suit private, within-package usage.
Correct Answer: from .utils import helper
Example Code
# inside package module
from .utils import helper
helper.run()
51. What is a common role of __init__.py in a package?
Difficulty: EasyType: MCQTopic: Imports Packages
- It compiles C extensions
- It marks a directory as a package and can expose the public API
- It disables relative imports
- It forces lazy imports for all modules
__init__.py runs on package import. You can import and re-export symbols there to present a clean interface.
In modern Python, implicit namespace packages can work without it, but __init__.py still helps for explicit control and side effects.
Correct Answer: It marks a directory as a package and can expose the public API
Example Code
# mypkg/__init__.py
from .core import run
__all__=['run']
52. Which object primarily controls the module search locations at runtime?
Difficulty: MediumType: MCQTopic: Imports Packages
- sys.argv
- sys.path
- os.environ
- site.PREFIXES
sys.path is a list of directories that the import system searches in order. You can inspect or modify it at runtime for advanced cases.
A better practice is to use packages and editable installs rather than mutating sys.path in production code.
Correct Answer: sys.path
Example Code
import sys
print(sys.path[0])
53. When should you use binary mode 'rb' or 'wb'?
Difficulty: EasyType: MCQTopic: File Handling
- When reading JSON
- When handling images or ZIP files
- When you need automatic newline conversion
- When you want implicit UTF eight decoding
Binary mode reads and writes raw bytes. Use it for non-text data like images, archives, or custom binary protocols.
Text mode decodes bytes to strings using an encoding and may translate newlines. That is ideal for human-readable text.
Correct Answer: When handling images or ZIP files
Example Code
with open('photo.jpg','rb') as f:
buf=f.read(1024)54. Explain how a context manager works in Python. Show a small custom example.
Difficulty: MediumType: SubjectiveTopic: Context Managers
A context manager defines enter and exit steps around a code block. Python calls the enter method before the block starts and exit after it ends, even when exceptions happen. This pattern centralizes resource handling and prevents leaks.
You can implement one by defining __enter__ and __exit__ methods or by using contextlib.contextmanager. Common uses include file handles, locks, temporary directories, and timing utilities.
Example Code
class Locker:
def __enter__(self): print('acquire'); return self
def __exit__(self, exc_type, exc, tb): print('release')
with Locker() as l:
do_work()55. When would you prefer pathlib over os.path? Explain the benefits.
Difficulty: MediumType: SubjectiveTopic: Pathlib Usage
pathlib provides object-oriented paths with clear operators and methods. Code becomes more readable, such as path slash 'file.txt' instead of string joins. It also handles Windows and POSIX differences for you.
Paths expose high-level actions like read_text, read_bytes, iterdir, and rename. This reduces boilerplate and avoids mistakes with separators and encodings.
Example Code
from pathlib import Path
p = Path('logs')/'app.log'
text = p.read_text(encoding='utf-8')56. How do you process a multi-gigabyte file efficiently in Python?
Difficulty: MediumType: SubjectiveTopic: File Handling
Stream data instead of loading it all. Iterate line by line, or read fixed-size chunks in binary for parsing. Keep memory usage stable and perform work incrementally. Use generators to pipe data through steps.
Tune buffering, avoid per-line string concatenation, and batch writes. If I O is the bottleneck, consider gzip streams, multiprocessing for CPU-bound parsing, or external tools for pre-filtering.
Example Code
def chunks(f, size=1024*1024):
while True:
b=f.read(size)
if not b: break
yield b
with open('big.bin','rb') as f:
for c in chunks(f):
process(c)57. Explain Python’s import caching and how to reload a module safely.
Difficulty: MediumType: SubjectiveTopic: Imports Packages
Imports are cached in sys.modules. Later imports reuse the already loaded module object, which speeds up startup and avoids duplicate singletons. This also means code changes are not seen until a restart.
To reload during development, use importlib.reload on the module object. Be careful: existing references keep pointing to old objects. In production, prefer a fresh process or a clean import path.
Example Code
import importlib, mymod
# edit mymod on disk
mymod = importlib.reload(mymod)
58. Design a small package structure and explain how to expose a clean public API.
Difficulty: MediumType: SubjectiveTopic: Package API
Organize code by feature: keep internals in submodules and re-export stable names in the package’s __init__.py. This gives users short imports while letting you refactor internals later. Use __all__ to declare what is public.
Document the public surface and keep breaking changes behind the package boundary. Internals can change, but the top-level API should remain stable for consumers.
Example Code
mypkg/
__init__.py # from .core import run; __all__=['run']
core.py
utils.py
59. Which order best describes Python's exception flow blocks?
Difficulty: EasyType: MCQTopic: Exception Flow
- try → except → else → finally
- try → else → except → finally
- except → try → else → finally
- try → finally → except → else
Code inside try runs first. If an exception occurs and matches a handler, the matching except runs. If no exception occurs, the else block runs.
The finally block always runs at the end for cleanup, whether an exception happened or not. This ensures resources are released in all cases.
Correct Answer: try → except → else → finally
Example Code
try:
do_work()
except ValueError:
fix()
else:
commit()
finally:
close()60. How do you catch multiple specific exception types in one handler?
Difficulty: EasyType: MCQTopic: Exception Flow
- except A or B:
- except (A, B):
- except A, B:
- except A | B:
Provide a tuple of exception classes in one except clause. Python checks the raised error against any of the types in the tuple.
This keeps handlers concise when the recovery action is identical for several related exceptions.
Correct Answer: except (A, B):
Example Code
try:
risky()
except (KeyError, IndexError):
recover()61. Why should you avoid a bare 'except:' in production code?
Difficulty: MediumType: MCQTopic: Exception Flow
- It is slower than typed handlers
- It only catches SyntaxError
- It hides SystemExit and KeyboardInterrupt by default
- It always re-raises exceptions
A bare except can swallow control flow exceptions such as SystemExit and KeyboardInterrupt. This prevents graceful shutdowns and can make debugging hard.
Prefer catching specific exception classes. If you must catch all, catch Exception explicitly and re-raise unknown cases.
Correct Answer: It hides SystemExit and KeyboardInterrupt by default
Example Code
try:
run()
except Exception as e:
log(e)
raise62. What does 'raise NewError from e' achieve?
Difficulty: MediumType: MCQTopic: Exception Flow
- It suppresses the original error
- It chains exceptions and keeps the original context
- It converts e to a warning
- It logs e then exits
Using from preserves the cause in the traceback as __cause__. It tells readers why the higher-level error happened.
This is useful when translating low-level errors into domain-specific ones while keeping a clear audit trail.
Correct Answer: It chains exceptions and keeps the original context
Example Code
try:
low()
except OSError as e:
raise RuntimeError('storage failed') from e63. What is a good base class for your application's custom exceptions?
Difficulty: EasyType: MCQTopic: Exceptions
- object
- BaseException
- Exception
- RuntimeError only
Derive custom errors from Exception, not from BaseException. BaseException is reserved for core control flow like SystemExit.
Having a single AppError base lets you catch all app-specific failures in one place when needed.
Correct Answer: Exception
Example Code
class AppError(Exception):
pass
class ConfigError(AppError):
pass64. When should you use 'assert' instead of raising a ValueError?
Difficulty: MediumType: MCQTopic: Exception Flow
- For user input validation in production
- For invariant checks that should be optimized away with O opt
- When logging is required
- When you need to retry
Assertions are for internal sanity checks that can be disabled with the optimize flag. They document assumptions for developers.
For user input and runtime validation, raise explicit exceptions so checks are always active.
Correct Answer: For invariant checks that should be optimized away with O opt
Example Code
def area(r):
assert r >= 0
return 3.14*r*r65. Why is logging preferred over print for errors?
Difficulty: EasyType: MCQTopic: Logging
- print is slower
- logging cannot write to files
- logging supports levels, handlers, and structured messages
- print only works on Windows
The logging module lets you route messages to files, streams, or remote systems with levels and formatting.
It integrates with exception info to capture tracebacks and supports configuration without changing source code.
Correct Answer: logging supports levels, handlers, and structured messages
Example Code
import logging
logging.exception('failed to save')66. What does contextlib.suppress(ValueError) do?
Difficulty: MediumType: MCQTopic: Context Managers
- Converts ValueError to None
- Reraises ValueError with cause
- Ignores ValueError within the with block
- Logs ValueError and exits
suppress catches the listed exception types inside a with block and ignores them. Code continues after the block without raising.
Use it for harmless, expected failures like best-effort cleanups. Avoid hiding real bugs by suppressing too broadly.
Correct Answer: Ignores ValueError within the with block
Example Code
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove('tmp.txt')67. Which pair correctly reflects Python's exception hierarchy?
Difficulty: MediumType: MCQTopic: Exceptions
- Exception is a subclass of BaseException
- BaseException is a subclass of Exception
- RuntimeError subclasses OSError
- KeyboardInterrupt subclasses Exception
BaseException sits at the root. Exception derives from it. SystemExit and KeyboardInterrupt also derive from BaseException, not from Exception.
This is why catching Exception does not trap KeyboardInterrupt by default.
Correct Answer: Exception is a subclass of BaseException
Example Code
print(issubclass(Exception, BaseException)) # True
68. Explain reliable resource cleanup with finally and how it compares to using a context manager.
Difficulty: MediumType: SubjectiveTopic: Exception Flow
finally runs no matter what, including when exceptions occur or returns happen. This makes it a reliable place to close files, release locks, or restore state. It protects cleanup from early exits and errors.
A context manager packages this pattern in a reusable form. The enter step acquires a resource, and the exit step guarantees release. Prefer with for common patterns and use finally when you need custom control flow in one place.
Example Code
f=None
try:
f=open('data.txt','r',encoding='utf-8')
use(f)
finally:
if f: f.close()69. How do you debug a failing function using pdb? Mention key commands.
Difficulty: MediumType: SubjectiveTopic: pdb basics
Insert a breakpoint where state looks suspicious and run the program. Use step to go into functions, next to move over lines, and continue to resume until the next breakpoint. Print variables with p, and inspect the call stack with where.
This interactive loop lets you confirm assumptions and isolate the exact line that changes program state. Once you see the wrong value or branch, you can fix the cause rather than the symptom.
Example Code
import pdb
pdb.set_trace()
result = compute(x)
# commands: n, s, c, p var, w
70. What is a traceback and how do you make it actionable in logs?
Difficulty: MediumType: SubjectiveTopic: traceback
A traceback is the recorded path of function calls that led to an exception. It shows the file, line numbers, and frames, ending at the point of failure. Reading it reveals both the symptom and the path that produced it.
In logs, include exception info and context. Capture variables that explain inputs, but avoid leaking secrets. Use logging.exception inside an except block so the traceback is included automatically.
Example Code
import logging
try:
risky()
except Exception:
logging.exception('risky failed with inputs=%s', args)71. Compare EAFP and LBYL in Python error handling. When is each better?
Difficulty: MediumType: SubjectiveTopic: Error Style
Easier to ask forgiveness than permission uses try and except. You attempt the operation and handle failure. It is concise and safe under races where the world can change between a check and an action.
Look before you leap tests conditions first. It can be clearer when errors are expensive or truly exceptional. In Python, prefer EAFP when interacting with dictionaries, files, or concurrency. Use LBYL for validation paths where exceptions would be noisy.
Example Code
# EAFP
d = {}
try:
v = d['x']
except KeyError:
v = 0
# LBYL
v = d['x'] if 'x' in d else 072. Outline a safe retry pattern for transient errors such as network timeouts.
Difficulty: MediumType: SubjectiveTopic: Retry Pattern
Catch known transient exceptions and retry with exponential backoff. Limit the number of attempts and add jitter to avoid thundering herds. Between attempts, release resources and check cancellation signals.
Log the final failure with context and re-raise a clear error. Do not retry on non-transient errors like authentication failures, and do not swallow exceptions silently.
Example Code
for attempt in range(5):
try:
return call()
except TimeoutError:
sleep(0.1 * 2**attempt)
raise RuntimeError('give up')73. What makes an object iterable in Python?
Difficulty: MediumType: MCQTopic: Iterators
- Defining __call__ only
- Defining __iter__ that returns an iterator
- Defining __next__ only
- Having a length via __len__
An object is iterable if it implements the iteration protocol. That means it defines dunder iter which returns an iterator. The iterator itself must implement dunder next.
In practice, containers return a fresh iterator from dunder iter, while custom iterator objects can return self when dunder iter is called.
Correct Answer: Defining __iter__ that returns an iterator
Example Code
class Box:
def __init__(self, items): self.items=items
def __iter__(self): return iter(self.items)74. Which statement is true for a custom iterator object?
Difficulty: MediumType: MCQTopic: Iterators
- It must implement only __next__
- It must implement __iter__ returning self and __next__
- It must be a generator function
- It must inherit from collections.Iterator
An iterator needs dunder iter that returns self and dunder next that yields the next value or raises StopIteration.
You do not need inheritance or a generator; any object with these methods is an iterator.
Correct Answer: It must implement __iter__ returning self and __next__
Example Code
class Count:
def __init__(self,n): self.i=0; self.n=n
def __iter__(self): return self
def __next__(self):
if self.i>=self.n: raise StopIteration
self.i+=1; return self.i75. Why choose a generator expression over a list comprehension?
Difficulty: EasyType: MCQTopic: Generators
- It always runs faster
- It precomputes results eagerly
- It saves memory by producing items lazily
- It sorts output automatically
A generator expression yields one item at a time. This keeps memory low for large streams or pipelines.
A list comprehension builds the whole list in memory first, which is fine for small data but expensive for huge inputs.
Correct Answer: It saves memory by producing items lazily
Example Code
total = sum(x*x for x in range(10_000_000))
76. In a generator, how does 'return value' behave?
Difficulty: MediumType: MCQTopic: Generators
- It yields one more item
- It sets StopIteration with the value attached
- It resets the generator
- It converts generator to list
A bare return stops the generator. A return with a value raises StopIteration carrying that value. It can be observed by a caller that drives the generator manually or via yield from.
This is useful for communicating a final result from a subgenerator to its delegator.
Correct Answer: It sets StopIteration with the value attached
Example Code
def g():
yield 1
return 99
# next after yield raises StopIteration(99)77. What does 'yield from subgen' do in a generator?
Difficulty: MediumType: MCQTopic: Generators
- Copies items eagerly from subgen
- Delegates iteration and forwards send and throw
- Flattens nested lists only
- Makes subgen run in a new thread
yield from passes through values, exceptions, and the close or send calls to the subgenerator. It also captures the subgenerator's return value as the expression result.
This makes generator composition simple and keeps control flow readable.
Correct Answer: Delegates iteration and forwards send and throw
Example Code
def outer():
result = yield from inner()
yield result78. Which itertools tool chains multiple iterables without copying?
Difficulty: MediumType: MCQTopic: Itertools
- accumulate
- product
- chain
- zip_longest
itertools.chain yields from each iterable in sequence without building a new list. It is ideal for streaming pipelines.
accumulate computes running totals. product makes cartesian products. zip_longest pairs uneven inputs with fill values.
Correct Answer: chain
Example Code
from itertools import chain
for x in chain([1,2],[3,4]):
print(x)79. What is a safe use case for functools.lru_cache?
Difficulty: MediumType: MCQTopic: Caching
- Caching functions with side effects
- Caching pure functions with repeated inputs
- Caching methods that mutate self
- Caching generators that yield once
lru_cache stores results for a function call based on arguments. It works best for pure functions where the same input always gives the same output.
Avoid caching when results depend on external state, time, or I O, unless you manage invalidation carefully.
Correct Answer: Caching pure functions with repeated inputs
Example Code
from functools import lru_cache
@lru_cache(maxsize=256)
def fib(n):
return n if n<2 else fib(n-1)+fib(n-2)80. What does functools.partial do?
Difficulty: EasyType: MCQTopic: Functools
- Converts a function to a generator
- Freezes some arguments to create a new callable
- Speeds up math by using C
- Wraps a function with retries
partial binds fixed positional or keyword arguments and returns a new callable with a smaller signature. This simplifies callbacks and adapters.
It is handy with map, event handlers, or when passing configuration into a function without lambdas.
Correct Answer: Freezes some arguments to create a new callable
Example Code
from functools import partial
pow2 = partial(pow, 2)
print(pow2(10)) # 1024
81. Which methods form the core descriptor protocol?
Difficulty: HardType: MCQTopic: Descriptors
- __get__, __set__, __delete__
- __call__, __repr__
- __enter__, __exit__
- __iter__, __next__
A descriptor is an object attribute with binding behavior defined by dunder get, dunder set, and dunder delete. Properties and functions use descriptors under the hood.
Use descriptors to implement reusable managed attributes such as validation or type enforcement.
Correct Answer: __get__, __set__, __delete__
Example Code
class NonEmpty:
def __set__(self, obj, val):
if not val: raise ValueError('empty')
obj.__dict__['name']=val
def __get__(self, obj, owner):
return obj.__dict__['name']82. Explain contextlib.contextmanager and when you would prefer it over a class-based context manager.
Difficulty: MediumType: SubjectiveTopic: Context Managers
contextmanager turns a simple generator into a context manager. The code before the yield is the setup. The code after the yield is the teardown that always runs, even if an exception occurs.
Use it when the resource logic is small and linear. It keeps the pattern concise without defining enter and exit methods. For complex state or multiple exit paths, a class-based manager can be clearer and easier to extend.
Example Code
from contextlib import contextmanager
@contextmanager
def opened(path):
f=open(path,'r',encoding='utf-8')
try:
yield f
finally:
f.close()83. How do you build a memory-efficient data pipeline with generators? Explain the pattern.
Difficulty: MediumType: SubjectiveTopic: Generators
Create a chain of small generator functions, each transforming or filtering items and yielding to the next stage. Because each stage yields on demand, only a few items live in memory at once.
Compose them with for loops or yield from so that back pressure controls flow. This pattern is ideal for log processing, streaming CSVs, and network streams where you cannot load everything at once.
Example Code
def read_lines(f):
for line in f: yield line.strip()
def only_errors(lines):
for s in lines:
if 'ERROR' in s: yield s
with open('app.log') as f:
for msg in only_errors(read_lines(f)): print(msg)84. When should you use async and await in Python and what are common pitfalls?
Difficulty: MediumType: SubjectiveTopic: Asyncio Basics
Use async for high-concurrency I O like sockets, HTTP calls, or databases with async drivers. The event loop schedules tasks that yield at await points, so many operations overlap without threads.
Pitfalls include calling blocking functions inside async code, which stalls the loop. Use run in executor or an async variant. Also ensure you await tasks or gather them; otherwise work may never run or errors may be lost.
Example Code
import asyncio, aiohttp
async def fetch(url):
async with aiohttp.ClientSession() as s:
async with s.get(url) as r:
return await r.text()
asyncio.run(fetch('https://example.com'))85. What are generator send and throw used for? Give a practical example.
Difficulty: HardType: SubjectiveTopic: Generators
send pushes a value into a paused generator and resumes it until the next yield. throw injects an exception at the yield point. Together they allow two-way communication and controlled stop conditions.
This is useful for coroutines in older code or for advanced pipelines that adjust behavior on feedback, such as changing a step size in a stream processor.
Example Code
def coro():
total=0
while True:
x = yield total
if x is None: break
total += x
c = coro(); next(c)
print(c.send(3)); print(c.send(4)); c.throw(GeneratorExit)86. When would you choose a custom descriptor over @property?
Difficulty: MediumType: SubjectiveTopic: Descriptors
Use property for one attribute on one class. It is simple and local. Choose a custom descriptor when the same validation or behavior must be reused across many classes or many attributes.
Descriptors centralize logic and remove repetition, such as non-empty strings, ranged numbers, or type-checked fields. They also work with class attributes and can coordinate with metaclasses if you need schema-like behavior.
Example Code
class NonEmpty:
def __set__(self,obj,val):
if not val: raise ValueError('empty')
obj.__dict__['name']=val
class User:
name = NonEmpty()
def __init__(self, name): self.name=name87. Which statement about shallow copy versus deep copy is correct for a nested list?
Difficulty: MediumType: MCQTopic: Copy Semantics
- Shallow copy duplicates all inner objects
- Deep copy shares inner lists to save memory
- Shallow copy copies only the outer container
- Both behave the same for immutable values
A shallow copy creates a new outer container but keeps references to the same inner objects. Editing an inner list through the copy also changes the original.
A deep copy recursively copies inner structures, so changes in the copy do not leak back. Pick deep copy when you need isolation.
Correct Answer: Shallow copy copies only the outer container
Example Code
import copy
orig=[[1],[2]]
sh=copy.copy(orig)
dp=copy.deepcopy(orig)
sh[0][0]=9 # orig also changes
# dp edits do not affect orig
88. Which function performs a deep copy of an arbitrary Python object graph?
Difficulty: EasyType: MCQTopic: Copy Semantics
- list()
- copy.copy()
- copy.deepcopy()
- object.copy()
copy.copy makes a shallow copy. copy.deepcopy walks the entire object graph and copies inner containers too.
For custom classes, you can guide deep copy with __deepcopy__ if you need special handling.
Correct Answer: copy.deepcopy()
Example Code
import copy
b = copy.deepcopy(a)
89. Which pair correctly lists a mutable and an immutable built-in type?
Difficulty: EasyType: MCQTopic: Mutability
- tuple and list
- list and tuple
- str and dict
- set and frozenset are both immutable
Lists are mutable and can be changed in place. Tuples are immutable and cannot be changed after creation.
Understanding mutability is key to safe copying, hashing, and function argument behavior.
Correct Answer: list and tuple
Example Code
lst=[1,2]; lst.append(3)
tup=(1,2) # cannot append
90. What does sys.getsizeof(x) report?
Difficulty: MediumType: MCQTopic: Memory Model
- Memory of x and all nested contents
- Only the size of referenced buffer without overhead
- The immediate size of the object header and payload
- Always the exact process memory usage
getsizeof returns the shallow size of the object in bytes. It does not include sizes of nested objects it references.
To estimate full footprint, you must traverse the object graph and sum sizes manually or use third party tools.
Correct Answer: The immediate size of the object header and payload
Example Code
import sys
print(sys.getsizeof([1,2,3]))
91. Why do small integers often appear to have the same id in Python sessions?
Difficulty: MediumType: MCQTopic: Memory Model
- Python stores all integers globally
- Small integers are interned and reused by the runtime
- id returns a random number
- Integers are always passed by reference
Implementations like CPython cache a range of small integers to reduce allocations. This makes repeated small numbers share the same identity.
It is an implementation detail, not a language guarantee. Do not rely on it for program logic.
Correct Answer: Small integers are interned and reused by the runtime
Example Code
a=5; b=5
print(id(a)==id(b)) # often True
92. When processing binary data in place, which type is appropriate?
Difficulty: EasyType: MCQTopic: Bytes Buffer
- bytes
- bytearray
- memoryview of bytes only
- str
bytes is immutable. bytearray is mutable and lets you edit binary data without creating new objects.
For zero-copy slicing of existing buffers, wrap with memoryview to avoid extra allocations.
Correct Answer: bytearray
Example Code
buf = bytearray(b'ABC')
buf[0]=97 # a
93. What is the main benefit of using memoryview on a large bytes object?
Difficulty: MediumType: MCQTopic: Bytes Buffer
- Automatic compression
- Sorted access
- Zero-copy slices and views
- Thread safety
memoryview exposes a shared view of the underlying buffer. Slices do not allocate new memory, which helps performance.
It is useful for protocols, image processing, and when slicing large binary payloads.
Correct Answer: Zero-copy slices and views
Example Code
data=b'x'*1_000_000
mv=memoryview(data)
chunk=mv[100:200]
94. How does defining __slots__ typically affect memory and attribute access?
Difficulty: HardType: MCQTopic: Slots
- Increases memory, slower access
- Reduces memory, may slightly speed access
- No effect on memory
- Prevents inheritance
__slots__ removes the per-instance dictionary and stores fields in a fixed structure. This cuts memory per instance and can make attribute lookups a bit faster.
It also limits dynamic attribute creation unless you include __dict__ in slots.
Correct Answer: Reduces memory, may slightly speed access
Example Code
class Row:
__slots__=('a','b')
def __init__(self,a,b): self.a=a; self.b=b95. Which tool is best for quick micro-benchmarks in pure Python?
Difficulty: EasyType: MCQTopic: Profiling
- cProfile
- timeit
- pdb
- tracemalloc
timeit runs a statement many times and reports stable timing by reducing noise. It is ideal for comparing small code snippets.
For finding hot functions in a program, use cProfile. For memory tracking, use tracemalloc.
Correct Answer: timeit
Example Code
import timeit
print(timeit.timeit('sum(range(1000))', number=1000))96. Explain how Python memory management works: reference counting, cycles, and the garbage collector.
Difficulty: MediumType: SubjectiveTopic: Garbage Collection
CPython primarily uses reference counting. Each object tracks how many references point to it. When the count drops to zero, the object is freed immediately. This makes most reclamation prompt and predictable.
Reference cycles cannot be collected by counting alone, so a cyclic garbage collector runs occasionally to find groups of objects that reference each other but are otherwise unreachable. You can tune collection thresholds with the gc module, but most apps do not need manual tweaks.
Be careful with objects that define finalizers; cycles containing them may not be collected automatically. Breaking cycles with weak references or context managers keeps memory healthy.
Example Code
import gc
print(gc.get_threshold())
# gc.collect() can force a cycle collection
97. What are weak references and why are they useful?
Difficulty: MediumType: SubjectiveTopic: Weak References
A weak reference lets you refer to an object without increasing its reference count. When the object is reclaimed, the weak reference becomes dead. This avoids memory leaks caused by accidental strong reference cycles.
Use weak references for caches, observer lists, and graphs where you do not own the lifetime. They allow objects to disappear naturally when nothing else needs them.
Example Code
import weakref
class A: pass
obj=A()
w=weakref.ref(obj)
obj=None # target can be collected
98. How do you find and fix performance bottlenecks in a Python program?
Difficulty: MediumType: SubjectiveTopic: Profiling
Start with measurement, not guesses. Use cProfile to collect function level timings and inspect results with pstats or snakeviz. Focus on the few functions that dominate total time, often called the vital few.
Apply targeted fixes: reduce algorithmic complexity, move heavy loops out of Python into vectorized libraries, or cache pure results. Re-measure after each change to confirm real wins and to avoid regressions.
Example Code
import cProfile, pstats
cProfile.run('main()', 'prof')
p=pstats.Stats('prof'); p.sort_stats('tottime').print_stats(20)99. When should you replace Python loops with vectorized operations or built-ins?
Difficulty: MediumType: SubjectiveTopic: NumPy Speed
Replace loops when you are doing simple numeric or array transformations that map well to C level operations. Libraries such as NumPy run inner loops in optimized C, often giving big speedups.
Even without NumPy, prefer built-ins like sum, min, max, any, all, and list comprehensions over manual loops. They are faster, clearer, and implemented in C where possible.
Example Code
nums=list(range(1_000_000))
# better
total=sum(nums)
# with numpy
# import numpy as np; total=np.sum(np.array(nums))
100. For performance, when do you choose threading versus multiprocessing in Python?
Difficulty: MediumType: SubjectiveTopic: Concurrency
Threads work best for I O bound tasks, like network calls or disk waits. While one thread blocks, others can run, and the program makes progress. The Global Interpreter Lock limits true parallel CPU execution, but I O overlap still helps a lot.
For CPU bound work, use multiprocessing to run multiple processes that bypass the Global Interpreter Lock. Share data carefully, batch work to reduce pickling costs, and consider using native extensions when possible.
Example Code
# I O bound: threading
# CPU bound: multiprocessing
# mix with concurrent.futures for a simple API
101. Does using 'del' free memory immediately? How do you diagnose leaks?
Difficulty: MediumType: SubjectiveTopic: Memory Leaks
del only deletes a name binding. If other references still exist, the object stays alive. When the last reference goes away, CPython frees it, or the cycle collector reclaims it later if part of a cycle.
To diagnose leaks, track object counts over time with tracemalloc or objgraph like tools. Look for growing containers, cache keys that never expire, or reference cycles involving long lived objects.
Example Code
import tracemalloc
tracemalloc.start()
# run workload
print(tracemalloc.get_traced_memory())
102. Why are NumPy operations often faster than pure Python loops?
Difficulty: MediumType: MCQTopic: NumPy Speed
- They run on the GPU by default
- They execute optimized C loops and use contiguous arrays
- They automatically use multiple processes
- They cache every result to disk
NumPy stores data in contiguous memory blocks and performs vectorized math in optimized C loops. This avoids Python’s per-element overhead and speeds up arithmetic and broadcasting.
The speedup is highest for large numeric arrays. For tiny arrays, Python overhead can dominate, and gains may be small.
Correct Answer: They execute optimized C loops and use contiguous arrays
Example Code
import numpy as np
x=np.arange(1_000_000)
y=x*2 # vectorized C loop
103. What is the key difference between .loc and .iloc in pandas?
Difficulty: EasyType: MCQTopic: Pandas Basics
- .loc is label based; .iloc is integer position based
- Both are label based
- .loc is integer based; .iloc is label based
- .iloc allows booleans; .loc does not
.loc uses index labels and supports boolean masks. .iloc uses zero-based integer positions. Mixing them up causes off-by-one errors or KeyErrors.
Prefer .loc when working with meaningful index labels such as dates or ids.
Correct Answer: .loc is label based; .iloc is integer position based
Example Code
df.loc['2025-01-01']
df.iloc[0:10]
104. Which pattern helps avoid pandas SettingWithCopy issues when assigning?
Difficulty: MediumType: MCQTopic: Pandas Gotchas
- Chained indexing like df[df.a>0]['b']=1
- Using .loc with a mask, then assign
- Convert to NumPy and assign
- Disable warnings and proceed
Chained indexing can operate on a view and silently skip updates. Use .loc with a boolean mask to target rows and columns unambiguously.
This produces predictable writes and keeps your code clear and safe.
Correct Answer: Using .loc with a mask, then assign
Example Code
m = df['a']>0
df.loc[m, 'b'] = 1
105. How do you compute multiple aggregations per column with groupby?
Difficulty: MediumType: MCQTopic: Pandas Basics
- Use groupby.sum only
- Use groupby.apply with custom loops
- Use .agg with a dict or list of funcs
- Use pivot without groupby
.agg accepts a list of functions per column or a dict mapping columns to functions. It creates a tidy summary in one pass.
This is faster and clearer than manual apply loops for standard aggregations.
Correct Answer: Use .agg with a dict or list of funcs
Example Code
df.groupby('city').agg({'sales':['sum','mean'],'id':'count'})106. Which option reduces memory when loading a large CSV with pandas?
Difficulty: MediumType: MCQTopic: Pandas Performance
- Avoid specifying dtypes
- Use dtype and usecols to limit types and columns
- Always set low_memory=False
- Always parse dates after loading
Specifying dtypes and selecting only needed columns cuts RAM usage. Parsing dates during read can also help if types are clear.
For huge files, consider chunksize to stream data and process in batches.
Correct Answer: Use dtype and usecols to limit types and columns
Example Code
pd.read_csv('data.csv', usecols=['id','price'], dtype={'id':'int32','price':'float32'})107. What is true about timeouts in the requests library?
Difficulty: EasyType: MCQTopic: HTTP Requests
- Requests has a safe default timeout
- You must pass timeout; otherwise it can wait forever
- Timeout only applies to connect, not read
- Timeout raises warnings, not exceptions
By default, requests may block indefinitely. Always pass a timeout as a tuple or number to bound connect and read waits.
This prevents hung workers and improves reliability in production services.
Correct Answer: You must pass timeout; otherwise it can wait forever
Example Code
import requests
r = requests.get(url, timeout=(3.05, 10))
108. What is the main benefit of using requests.Session?
Difficulty: MediumType: MCQTopic: HTTP Requests
- Automatic retries for all errors
- Connection pooling and shared headers or cookies
- Async I O support
- Built-in rate limiting
A Session reuses TCP connections and carries shared headers, auth, and cookies across calls. This reduces latency and keeps stateful interactions simple.
For retries, attach a Retry adapter from urllib3; it is not automatic.
Correct Answer: Connection pooling and shared headers or cookies
Example Code
s=requests.Session(); s.headers.update({'Authorization':'Bearer X'})
s.get(url)109. How do you make a quantifier non-greedy in Python regex?
Difficulty: EasyType: MCQTopic: Regex Basics
- Use + at end
- Add ? after the quantifier
- Use ^ anchor
- Compile with DOTALL
Adding question mark to star, plus, or brace quantifiers switches them to the shortest match. For example, star question matches as little as possible.
This is useful for extracting the smallest block between tags or quotes.
Correct Answer: Add ? after the quantifier
Example Code
import re
re.findall('<p>.*?</p>', html, flags=re.S)110. Why use raw strings like r"\d+" for regex patterns?
Difficulty: EasyType: MCQTopic: Regex Basics
- They run faster
- They allow backslashes without Python interpreting escapes
- They ignore whitespace
- They auto-compile the pattern
Raw strings treat backslashes as literal characters so regex escapes remain intact. This avoids accidental Python escape sequences.
It improves readability and prevents common mistakes like using "\n" when you meant backslash and n.
Correct Answer: They allow backslashes without Python interpreting escapes
Example Code
pattern = re.compile(r'\d{4}-\d{2}-\d{2}')111. Which statement about json vs pickle is correct for APIs?
Difficulty: MediumType: MCQTopic: JSON Safety
- pickle is safer for untrusted data
- json is interoperable and safer to load from clients
- json cannot handle lists
- pickle is human readable
JSON is language-neutral and safe when parsed with standard libraries. pickle can execute code on load and must not be used with untrusted inputs.
Use JSON for API payloads, with custom encoders for datetimes when needed.
Correct Answer: json is interoperable and safer to load from clients
Example Code
import json
payload=json.dumps({'ok':True})112. Explain why you would use requests.Session in a production client and how you would add retries.
Difficulty: MediumType: SubjectiveTopic: HTTP Requests
A Session keeps TCP connections open between calls. This cuts handshake overhead and improves throughput. It also centralizes shared headers, cookies, and auth, making code cleaner and reducing duplication.
To add retries, mount an HTTPAdapter with a Retry policy from urllib3. Configure backoff, allowed methods, and status codes like 429 or 503. This gives robust behavior under transient failures without rewriting call sites.
Example Code
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
s=requests.Session()
retry=Retry(total=3, backoff_factor=0.5, status_forcelist=[429,502,503,504])
s.mount('https://', HTTPAdapter(max_retries=retry))113. When should you prefer vectorized operations over DataFrame.apply, and what are practical tips?
Difficulty: MediumType: SubjectiveTopic: Pandas Performance
Prefer vectorized operations and built-ins like where, clip, str, and dt for series-wise transforms. They run in optimized C and avoid per-row Python overhead. apply is flexible but slower since it calls Python for each row or column.
If you must use apply, limit it to column-wise functions and avoid row-wise lambdas. For very large data, consider cython, numba, or pushing work into database engines when possible.
Example Code
df['z'] = (df['x']+df['y']).clip(lower=0)
# slower
# df['z']=df.apply(lambda r: max(r.x+r.y,0), axis=1)
114. How do named groups improve regex maintainability? Give an example.
Difficulty: MediumType: SubjectiveTopic: Regex Basics
Named groups label captured parts, so your code reads by meaning rather than index numbers. This reduces off-by-one errors and makes future edits safer.
You can access groups by name in match dictionaries, which is clearer for complex patterns such as dates, ids, or log lines.
Example Code
m=re.search(r'(?P<yyyy>\d{4})-(?P<mm>\d{2})-(?P<dd>\d{2})', s)
year=m.group('yyyy')115. Explain naive vs aware datetimes and how to handle time zones in modern Python.
Difficulty: MediumType: SubjectiveTopic: Datetime TZ
A naive datetime has no time zone info, so arithmetic and comparisons can be wrong across regions. An aware datetime carries a tzinfo and knows its offset from UTC, which makes math and conversions correct.
Use zoneinfo (built in) to attach real time zones, convert to UTC for storage, and convert to local zones for display. Always specify a timezone when parsing external timestamps.
Example Code
from zoneinfo import ZoneInfo
from datetime import datetime
now=datetime.now(tz=ZoneInfo('Asia/Kolkata'))116. How do you process a 10 GB CSV with pandas on a 4 GB machine?
Difficulty: MediumType: SubjectiveTopic: Pandas Performance
Stream it in chunks. Use read_csv with chunksize to process piece by piece, aggregate partial results, and combine at the end. Restrict columns with usecols and set precise dtypes to save RAM.
If parsing dates, specify parse_dates to avoid post-processing overhead. For compute heavy steps, push aggregation into a database or use Dask for out-of-core scaling.
Example Code
it=pd.read_csv('big.csv', chunksize=200_000, usecols=['id','amt'], dtype={'id':'int32','amt':'float32'})
for chunk in it:
process(chunk)117. Outline a robust pipeline to fetch paginated API data and build a DataFrame.
Difficulty: MediumType: SubjectiveTopic: Streaming Data
Use a Session with timeouts and a retry adapter. Fetch pages in a loop until no next link remains. Validate status codes and parse JSON safely. Accumulate records into a list of dicts or write pages to disk to cap memory.
Finally, build a DataFrame from the collected rows and normalize nested fields with json_normalize. Persist as Parquet with a stable schema for fast reads later.
Example Code
rows=[]; url=BASE
while url:
r=s.get(url, timeout=10)
data=r.json(); rows+=data['items']
url=data.get('next')
df=pd.DataFrame(rows)118. With extra space allowed, what is the optimal time complexity to solve Two Sum on an unsorted array?
Difficulty: EasyType: MCQTopic: Algo Patterns
- O(n²)
- O(n log n)
- O(n)
- O(log n)
Use a hash map from number to index while scanning once. For each value, check if target minus value exists in the map. This gives linear time because each lookup is constant time on average.
Sorting would make it O(n log n) and nested loops are quadratic. The hash map approach is the standard answer in interviews.
Correct Answer: O(n)
Example Code
def two_sum(nums, target):
pos = {}
for i, x in enumerate(nums):
y = target - x
if y in pos:
return pos[y], i
pos[x] = i
return None119. What is the most Pythonic way to check if two strings are anagrams?
Difficulty: EasyType: MCQTopic: Algo Patterns
- Sort and compare only
- Compare sets of characters
- Use collections.Counter to compare counts
- Reverse one string and compare
Anagrams have the same character counts. Counter compares frequency maps in linear time. Sorting also works but is O(n log n) and less direct.
Comparing sets fails because it ignores frequencies. Reversing does not relate to anagrams.
Correct Answer: Use collections.Counter to compare counts
Example Code
from collections import Counter
def is_anagram(a,b):
return Counter(a) == Counter(b)120. Which task is a classic fit for the sliding window pattern?
Difficulty: MediumType: MCQTopic: Sliding Window
- Longest substring without repeating characters
- Topological sort of a DAG
- Matrix multiplication
- Dijkstra shortest path
Sliding window keeps a moving range and updates state as the window grows or shrinks. It shines for subarray or substring problems with local constraints.
Graph algorithms and matrix math do not fit this pattern well.
Correct Answer: Longest substring without repeating characters
Example Code
def longest_unique(s):
seen, left, best = {}, 0, 0
for right, ch in enumerate(s):
if ch in seen and seen[ch] >= left:
left = seen[ch] + 1
seen[ch] = right
best = max(best, right-left+1)
return best121. Which function finds the leftmost index to insert a value into a sorted list?
Difficulty: EasyType: MCQTopic: Binary Search
- bisect_right
- heapq.nlargest
- bisect_left
- sorted_insert
bisect_left returns the first index where the value can be inserted to keep order. It is the usual tool for lower bound queries.
bisect_right returns the insertion point after any existing equal items.
Correct Answer: bisect_left
Example Code
from bisect import bisect_left
idx = bisect_left([1,2,2,3], 2) # 1
122. Is Python's built-in sort stable and which algorithm does it use?
Difficulty: EasyType: MCQTopic: Sorting
- Not stable, Quick sort
- Stable, TimSort
- Not stable, Heap sort
- Stable, Merge sort only
list.sort and sorted use TimSort, which is stable. Equal keys keep their original relative order.
Stability enables multi-key sorts by sorting on a secondary key first, then on the primary key.
Correct Answer: Stable, TimSort
Example Code
data=[('bob',3),('bob',1)]
print(sorted(data, key=lambda t:t[0]))123. Efficiently get the top k largest elements from a large list without fully sorting it. Which is best?
Difficulty: MediumType: MCQTopic: Heap Queue
- sorted(lst)[:k]
- heapq.nlargest(k, lst)
- reversed(sorted(lst))
- list.sort(); lst[:k]
heapq.nlargest uses a small heap of size k and runs faster than sorting everything when k is much smaller than n.
Full sorts are O(n log n). nlargest runs in O(n log k).
Correct Answer: heapq.nlargest(k, lst)
Example Code
import heapq
best = heapq.nlargest(3, [5,1,8,4,9])
124. How do you remove duplicates while preserving original order in Python 3.7+?
Difficulty: MediumType: MCQTopic: Dedupe Order
- list(set(lst))
- sorted(set(lst))
- dict.fromkeys(lst).keys()
- set comprehension then list
Dictionaries preserve insertion order. dict.fromkeys keeps the first time each item appears. You can wrap keys with list to get a list back.
Using set loses order. Sorting changes order entirely.
Correct Answer: dict.fromkeys(lst).keys()
Example Code
unique = list(dict.fromkeys([3,1,3,2,1])) # [3,1,2]
125. What is the most concise way to compute a frequency map of items?
Difficulty: EasyType: MCQTopic: Counter Tool
- collections.Counter(items)
- Manual dict with loops
- Pandas value_counts
- Set then count
Counter builds a frequency map in a single call and offers helpers like most_common.
Manual loops are longer and more error prone for this common task.
Correct Answer: collections.Counter(items)
Example Code
from collections import Counter
freq = Counter(['a','b','a'])
126. How do you sort students by name ascending, then score descending in one pass?
Difficulty: MediumType: MCQTopic: Sorting
- Use cmp with a custom comparator
- Sort twice due to stability only
- Use key=lambda s: (s.name, -s.score)
- Reverse=True and key on name
Provide a tuple key where the first element sorts by name ascending and the second uses negative score to get descending order.
cmp is not supported directly in Python 3, and sorting twice works but is less direct here.
Correct Answer: Use key=lambda s: (s.name, -s.score)
Example Code
students = sorted(students, key=lambda s: (s.name, -s.score))
127. Explain fixed and variable sliding window with an example problem.
Difficulty: MediumType: SubjectiveTopic: Sliding Window
A fixed window keeps a constant size and slides forward, updating a running state. For example, maximum sum of k-size subarray: add the new right item and remove the left item as the window moves. This gives linear time with constant extra space.
A variable window grows and shrinks based on a rule. For longest substring without repeats, expand right while all characters are unique. When a repeat appears, shrink left until valid again. This balances correctness and speed in a single pass.
Example Code
def max_sum_k(nums, k):
s = sum(nums[:k]); best = s
for i in range(k, len(nums)):
s += nums[i] - nums[i-k]
best = max(best, s)
return best128. Describe the prefix sum plus hash map technique to count subarrays with sum equal to K.
Difficulty: MediumType: SubjectiveTopic: Prefix Sum
Maintain a running prefix total. For each index j, you want some i where prefix j minus prefix i equals K. Rearranged, prefix i equals prefix j minus K. Keep a map from prefix value to the number of times you have seen it. Add the count of prefix j minus K to the answer and then record prefix j.
This works with negative and positive numbers and runs in linear time. It replaces nested loops with a single scan and a dictionary.
Example Code
from collections import defaultdict
def count_subarrays_k(nums, K):
pref, seen, ans = 0, defaultdict(int), 0
seen[0] = 1
for x in nums:
pref += x
ans += seen[pref-K]
seen[pref] += 1
return ans129. Give a safe binary search template to find the first index where arr[i] >= target and explain off-by-one traps.
Difficulty: MediumType: SubjectiveTopic: Binary Search
Use a left closed, right open range so the loop is easy to reason about. While left is less than right, take mid as left plus right minus left floor divided by two. If arr mid is less than target, move left to mid plus one. Else move right to mid. At exit, left is the first index with value greater than or equal to target.
Off-by-one bugs come from mixing closed intervals or updating both pointers the same way. Keeping a consistent invariant that the answer is always within the half open interval helps avoid mistakes.
Example Code
def lower_bound(a, target):
lo, hi = 0, len(a)
while lo < hi:
mid = (lo + hi) // 2
if a[mid] < target:
lo = mid + 1
else:
hi = mid
return lo130. Explain the two pointers pattern on a sorted array for finding a pair with a target sum. Compare with the hash map method.
Difficulty: MediumType: SubjectiveTopic: Two Pointers
With two pointers, place one at the left and one at the right. If the sum is too small, move left forward; if too big, move right backward. This finds a pair in linear time and constant extra space on sorted data.
The hash map method also runs in linear time without sorting, but it uses extra space and does not give sorted order guarantees. If sorting is allowed or already given, two pointers is simpler and more space efficient.
Example Code
def pair_sum_sorted(a, target):
i, j = 0, len(a)-1
while i < j:
s = a[i] + a[j]
if s == target: return i, j
if s < target: i += 1
else: j -= 1
return None131. Explain Kadane’s algorithm for maximum subarray sum and why it works.
Difficulty: MediumType: SubjectiveTopic: Kadane Algo
Kadane keeps a running best ending at the current index. Either extend the previous sum by adding the current number, or start fresh from the current number, whichever is larger. Track the global best seen so far.
It works because any negative running sum only hurts future totals, so dropping it and starting at the current index cannot reduce the best answer. The result comes in linear time with constant space.
Example Code
def kadane(nums):
best = cur = nums[0]
for x in nums[1:]:
cur = max(x, cur + x)
best = max(best, cur)
return best132. Why do we use a virtual environment (venv) for a Python service?
Difficulty: EasyType: MCQTopic: Env Setup
- To speed up the CPU
- To isolate dependencies from the system Python
- To compile extensions automatically
- To avoid writing tests
A virtual environment gives your project its own interpreter and site-packages. This avoids version conflicts with global packages and keeps builds reproducible.
It also makes deployments predictable: you freeze exact versions and recreate the same environment everywhere.
Correct Answer: To isolate dependencies from the system Python
Example Code
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
133. For public modules inside a package, which import style is preferred for clarity and stability?
Difficulty: EasyType: MCQTopic: Imports Packages
- Implicit relative imports
- Absolute imports from the package root
- Editing sys.path at runtime
- Wildcard imports everywhere
Absolute imports are explicit and unambiguous. They make refactors safer and work cleanly with tools and type checkers.
Use relative imports for private, intra-package internals when you want to signal non-public APIs.
Correct Answer: Absolute imports from the package root
Example Code
from myapp.core.service import run
# Prefer absolute over: from .core import service
134. Which order lists Python logging levels from lowest to highest?
Difficulty: MediumType: MCQTopic: Logging
- CRITICAL < ERROR < WARNING < INFO < DEBUG
- DEBUG < INFO < WARNING < ERROR < CRITICAL
- INFO < DEBUG < WARNING < ERROR < CRITICAL
- WARNING < DEBUG < INFO < ERROR < CRITICAL
Use level to control noise. DEBUG for detailed diagnostics, INFO for state changes, WARNING for odd states, ERROR for failures, CRITICAL for system-wide outages.
Pick one logger per module and avoid print in production so messages can be routed to files or systems.
Correct Answer: DEBUG < INFO < WARNING < ERROR < CRITICAL
Example Code
import logging
logging.basicConfig(level=logging.INFO)
log = logging.getLogger(__name__)
log.info('service started')135. According to Twelve-Factor principles, where should app configuration live?
Difficulty: EasyType: MCQTopic: App Config
- Hard-coded in source
- In environment variables
- In comments near the function
- In a README file only
Env vars let you change configuration per environment without code edits. They are easy to inject in containers and CI/CD.
Read them once at startup and validate. Do not hard-code secrets or hostnames in code.
Correct Answer: In environment variables
Example Code
import os
DB_URL = os.environ['DATABASE_URL']
DEBUG = os.getenv('DEBUG','0')=='1'136. In modern Python packaging, which file declares the build system and project metadata?
Difficulty: MediumType: MCQTopic: Packaging
- setup.cfg
- requirements.txt
- pyproject.toml
- Pipfile
pyproject.toml centralizes project metadata, dependencies, and the build backend. It is the standard way to define how a package is built and installed.
requirements.txt still helps pin runtime deps for apps, but pyproject describes the package itself.
Correct Answer: pyproject.toml
Example Code
[project]
name = 'myapp'
version = '0.1.0'
[build-system]
requires = ['setuptools','wheel']
build-backend = 'setuptools.build_meta'
137. What is the main benefit of adding type hints to a codebase?
Difficulty: MediumType: MCQTopic: Type Hints
- Runtime speed always doubles
- They enable static analysis and better tooling
- They remove the need for tests
- They replace docstrings
Hints help editors, linters, and CI catch mismatches before runtime. They also act as living documentation that improves readability.
Use a checker like mypy or pyright. Hints do not enforce types at runtime unless you add validators.
Correct Answer: They enable static analysis and better tooling
Example Code
from typing import List
def total(xs: List[int]) -> int:
return sum(xs)138. When should an API return HTTP 201 Created instead of 200 OK?
Difficulty: EasyType: MCQTopic: REST Basics
- On successful resource creation
- On any successful GET request
- Only when an error occurred
- When the client is unauthenticated
201 indicates a new resource was created. Include a Location header pointing to the new resource.
Use 200 for a successful read or update where nothing new was created.
Correct Answer: On successful resource creation
Example Code
# pseudo FastAPI
return JSONResponse(obj, status_code=201, headers={'Location': url})139. Which HTTP method is idempotent by design for updating a full resource?
Difficulty: MediumType: MCQTopic: API Design
PUT replaces the resource state, so repeating the same PUT has the same final effect. POST is not idempotent because each call may create or change additional state.
PATCH is partial update and may or may not be idempotent based on semantics.
Correct Answer: PUT
Example Code
# Request
PUT /users/42 {"name":"A"}
# Repeat same request → same final state140. In pytest, what is a good way to isolate external services during unit tests?
Difficulty: MediumType: MCQTopic: Testing
- Hit the real service with timeouts
- Use monkeypatch or mocks to replace network calls
- Run tests without assertions
- Sleep to avoid flakiness
Unit tests should be fast and deterministic. Replace outbound calls with fakes or fixtures so tests do not depend on the network.
Keep a separate suite for integration tests against real services.
Correct Answer: Use monkeypatch or mocks to replace network calls
Example Code
def test_client(monkeypatch):
monkeypatch.setattr('client.http_get', lambda url: {'ok':True})
assert my_call()==True141. Propose a clean Python project layout for a medium service and explain why it scales.
Difficulty: MediumType: SubjectiveTopic: Project Layout
Use a src layout with a top-level package for code, and tests as a sibling. Group code by feature or layer: api, core, adapters, and shared utils. Keep entry points thin and route all logic through the core domain layer.
This layout isolates concerns, avoids import path hacks, and lets you publish the package if needed. It also makes testing easy because each module has a clear dependency direction toward the core.
Example Code
project/
pyproject.toml
src/myapp/{api,core,adapters,utils}
tests/{unit,integration}142. Describe an error-handling strategy across layers (API, service, data).
Difficulty: MediumType: SubjectiveTopic: Error Handling
Define custom exception types for domain errors and translate them at the API edge to proper HTTP codes. Log once with enough context but without secrets. Avoid logging the same error repeatedly in lower layers.
At the service layer, raise precise exceptions and keep stack traces. At the data layer, wrap low-level errors with cause to preserve context. This gives clean responses to clients and debuggable traces for engineers.
Example Code
class NotFoundError(Exception): ...
# API edge
try:
svc.get_user(id)
except NotFoundError:
return 404, {'error':'not found'}143. How do you apply dependency injection (DI) in Python without a heavy framework?
Difficulty: MediumType: SubjectiveTopic: DI Pattern
Pass interfaces or callables into constructors or function parameters. Build the object graph at startup in a small composition root. Use factories for resources like clients or repositories.
This keeps modules decoupled and testable. In tests, swap real dependencies for fakes by passing different constructors or using monkeypatch.
Example Code
class Service:
def __init__(self, repo): self.repo=repo
def build():
repo = SqlRepo(url)
return Service(repo)144. What are safe ways to manage secrets for a Python app?
Difficulty: MediumType: SubjectiveTopic: Secret Mgmt
Never commit secrets to source control. Load them from environment variables, secret managers, or an encrypted vault. Limit scope using least privilege and rotate keys regularly.
In code, read once at startup, validate presence, and avoid printing values. In logs and errors, mask tokens to prevent leaks.
Example Code
import os
API_KEY = os.environ['PAYMENT_KEY'] # pulled from a secret store in production
145. When would you choose asyncio over threads for a backend service, and what pitfalls matter?
Difficulty: MediumType: SubjectiveTopic: Concurrency
Choose asyncio for high-concurrency I O: many sockets, HTTP calls, or database drivers with async support. It scales with a single event loop and has low context-switch overhead.
Do not call blocking code inside async paths; offload it to a worker pool. Always await tasks or gather them; leaked tasks hide errors. For CPU-bound work, use processes or native extensions instead of the event loop.
Example Code
import asyncio
async def main():
await asyncio.gather(fetch(a), fetch(b))
asyncio.run(main())