15 questions with detailed answers
Q1. What are the key features of Python?
Answer: Python is a high-level, multi-paradigm language known for readable syntax, an interpreted interactive workflow, and a huge ecosystem (PyPI). It is cross-platform, modular via packages, dynamically typed with optional type hints (3.5+), and manages memory automatically (reference counting + cyclic GC). It supports OOP, functional, and procedural styles, and can be extended with C/C++/Rust for performance-critical work. Common domains: AI/ML, web backends, automation, and scientific computing.
Q2. How is Python executed?
Answer: CPython does not run source directly on the CPU. It compiles .py to platform-independent bytecode (often cached as .pyc in __pycache__), then the Python Virtual Machine (PVM) interprets that bytecode. Compilation goes through lexing, parsing (AST), semantic analysis, and bytecode generation. Unlike C++ native binaries, bytecode is a higher-level portable form. Python 3.11+ adds a specializing adaptive interpreter for hot code; 3.13 adds an experimental copy-and-patch JIT. Use dis.dis() to inspect bytecode (e.g. constant folding of 15*20 into LOAD_CONST 300).
Q3. What is PEP 8 and why is it important?
Answer: PEP 8 is the official Python style guide. It standardizes formatting so code is consistent and readable—important because code is read far more often than it is written. Core rules: 4-space indentation (no tabs), ~79-char lines (Black/Ruff often use 88/100), CapWords for classes, snake_case for functions/variables, UPPER_CASE for constants, spaces around operators, and triple-quote docstrings for public APIs. Tools like Ruff, Black, and flake8 enforce it automatically.
Q4. How is memory allocation and garbage collection handled in Python?
Answer: Objects live on a private heap managed by the Python Memory Manager. Small objects (≤512 bytes) use obmalloc (arenas → pools → blocks); larger ones use C malloc. Primary GC is reference counting (ob_refcnt); when it hits zero, memory is freed immediately. Circular references are collected by the generational gc module (G0/G1/G2). Python 3.12+ immortal objects keep fixed refcounts for shared constants. Compared with C, this is safer (fewer leaks/dangling pointers) but has object overhead and occasional GC pauses.
Q5. What are the built-in data types in Python?
Answer: Immutable: int (arbitrary precision), float, complex, bool, str, tuple, frozenset, bytes, range, and NoneType (None). Mutable: list, set, dict (insertion-ordered since 3.7+), bytearray, and memoryview. Related stdlib types often discussed in interviews: array.array, collections.deque, types.SimpleNamespace. Pick by mutability, hashability (for dict keys/set members), and whether you need ordered sequences, unique sets, or key-value maps.
Q6. Explain the difference between a mutable and immutable object.
Answer: Mutable objects (list, set, dict, bytearray) can change in place; id() stays the same. Immutable objects (int, float, str, tuple, frozenset, bytes, bool) cannot be altered—operations create new objects with new identities. Only immutable (hashable) values can be dict keys or set elements. Mutables are efficient for in-place updates; immutables give safer sharing and enable interning for some ints/strings. With pass-by-assignment, mutating a mutable argument inside a function affects the caller; rebinding an immutable does not.
Q7. How do you handle exceptions in Python?
Answer: Use try / except / else / finally: try for risky code, except for specific errors (avoid bare except), else when no exception occurred, finally for cleanup. Prefer with context managers so resources close reliably. Raise with raise, and chain causes via raise NewError(...) from e. Python 3.11+ adds ExceptionGroup with except* for concurrent failures, and exception.add_note() for context. Optional: sys.excepthook for unhandled errors. In loops, continue in except to skip bad items.
Q8. What is the difference between list and tuple?
Answer: Lists are mutable sequences ([])—you can append, remove, and reassign elements. Tuples are immutable (())—structure and element bindings cannot change after creation. Tuples use less memory (no over-allocation), create/iterate slightly faster, and are hashable when all elements are hashable (usable as dict keys). Prefer lists for growing/homogeneous collections; tuples for fixed records, safer APIs, and mapping keys.
Q9. How do you create a dictionary in Python?
Answer: Dicts map unique hashable keys to values with O(1) average lookup and guaranteed insertion order (3.7+). Create with literals {"a": 1}, dict(a=1), dict([("a", 1)]), comprehensions {k: v for ...}, dict(zip(keys, values)), or dict.fromkeys(keys, default). Keys must be hashable (str, int, frozenset, or tuples of hashables); values can be any type.
Q10. What is the difference between == and is in Python?
Answer: == tests value equality (usually via __eq__). is tests identity—whether two names refer to the same object (same id()). Two equal lists can be == True but is False. Use == for normal comparisons; use is mainly for singletons, especially None (if x is None). Avoid is for ints/strings—interning makes results implementation-dependent.
Q11. How does a Python function work?
Answer: def creates a first-class function object (bytecode in __code__). On call, a frame is pushed, arguments are bound by assignment (mutable args can be mutated in place), the PVM runs the body, then the frame is popped and the return value (or None) is passed back. Name lookup follows LEGB: Local, Enclosing, Global, Built-in. Closures keep enclosing cells; decorators wrap callables. Recursion depth is limited (often ~1000) to protect the C stack. Prefer pure functions; use nonlocal/global sparingly.
Q12. What is a lambda function, and where would you use it?
Answer: lambda creates a small anonymous function limited to a single expression with an implicit return: lambda args: expression. Common uses: key= for sorted/min/max, short map/filter predicates, and one-off callbacks. PEP 8 discourages assigning lambdas to names—use def instead. Limitations: no statements/docstrings, and tracebacks show <lambda>, so prefer named functions for non-trivial logic.
Q13. Explain *args and **kwargs in Python.
Answer: *args packs extra positional arguments into a tuple; **kwargs packs extra keyword arguments into a dict. Typical uses: flexible APIs, forwarding args, and superclass/decorator wrappers. Signature order: positional params, *args, keyword-only params, **kwargs. A bare * forces later params to be keyword-only. Names args/kwargs are convention; the * and ** matter.
Q14. What are decorators in Python?
Answer: Decorators are higher-order functions (or classes) that wrap another callable to add behavior without changing its source. @decorator is sugar for func = decorator(func). Uses: auth, logging, timing, caching (@functools.cache), validation, rate limiting. Always apply @functools.wraps(func) so __name__/__doc__/annotations stay correct. Decorators with arguments need an extra factory layer (e.g. @repeat(times=3)).
Q15. How can you create a module in Python?
Answer: Any .py file is a module (name = filename without extension). Group modules in a package directory; include __init__.py for a regular package (namespace packages can omit it). Import with import m or from m import name; avoid from m import * to prevent namespace clashes. Guard runnable demos with if __name__ == "__main__": so import does not execute CLI/test code. Prefer type hints and clear public APIs for reusable modules.