📚 What You'll Find Here
- 50+ essential Python interview questions organized by topic and difficulty
- Detailed answers with code examples and explanations
- Common pitfalls and best practices for each question
- Tips for behavioral and technical interview preparation
- Real-world scenarios and problem-solving strategies
- Questions categorized from beginner to advanced levels
1. Python Basics (Beginner Level)
Answer:
Python is a high-level, interpreted, general-purpose programming language created by Guido van Rossum in 1991.
Key Features:
- Easy to Learn: Simple, readable syntax similar to English
- Interpreted: Code executes line by line, no compilation needed
- Dynamically Typed: No need to declare variable types
- Object-Oriented: Supports classes and objects
- Extensive Libraries: Rich standard library and third-party packages
- Cross-Platform: Runs on Windows, Mac, Linux
- Open Source: Free to use and distribute
Answer:
Python 3 is the current and future of Python. Python 2 reached end-of-life on January 1, 2020.
| Feature | Python 2 | Python 3 |
|---|---|---|
| Print Statement | print "Hello" |
print("Hello") |
| Integer Division | 5/2 = 2 |
5/2 = 2.5 (use // for floor division) |
| Unicode | ASCII by default | Unicode (UTF-8) by default |
| Range Function | range() returns list |
range() returns iterator |
| Error Handling | except Exception, e: |
except Exception as e: |
Answer:
Python has several built-in data types organized into categories:
# Numeric Types x = 10 # int y = 3.14 # float z = 1 + 2j # complex # Text Type name = "Python" # str # Sequence Types my_list = [1, 2, 3] # list (mutable) my_tuple = (1, 2, 3) # tuple (immutable) my_range = range(5) # range # Mapping Type my_dict = {"key": "value"} # dict # Set Types my_set = {1, 2, 3} # set (mutable) frozen = frozenset([1, 2, 3]) # frozenset (immutable) # Boolean Type is_true = True # bool # None Type nothing = None # NoneType # Binary Types binary = b"hello" # bytes byte_array = bytearray(5) # bytearray mem_view = memoryview(b"hello") # memoryview
Answer:
| Feature | List | Tuple |
|---|---|---|
| Mutability | Mutable (can be modified) | Immutable (cannot be modified) |
| Syntax | Square brackets [1, 2, 3] |
Parentheses (1, 2, 3) |
| Performance | Slower | Faster (due to immutability) |
| Memory | More memory | Less memory |
| Use Case | When data needs to change | When data should remain constant |
# List - Mutable my_list = [1, 2, 3] my_list[0] = 10 # ✓ Allowed my_list.append(4) # ✓ Allowed print(my_list) # [10, 2, 3, 4] # Tuple - Immutable my_tuple = (1, 2, 3) # my_tuple[0] = 10 # ✗ TypeError: 'tuple' object does not support item assignment # Tuples can be used as dictionary keys (lists cannot) coordinates = { (0, 0): "origin", (1, 0): "x-axis" }
Answer:
Unlike other languages that use braces {}, Python uses indentation to define code blocks. This enforces readable code.
# Correct indentation def greet(name): if name: print(f"Hello, {name}!") return True else: print("No name provided") return False # Incorrect indentation def bad_function(): print("This will cause IndentationError") # ✗ No indentation def mixed_function(): print("Using spaces") print("Wrong indentation") # ✗ Inconsistent
2. Data Structures
Answer:
There are multiple ways to reverse a list:
# Method 1: reverse() method (modifies in place) numbers = [1, 2, 3, 4, 5] numbers.reverse() print(numbers) # [5, 4, 3, 2, 1] # Method 2: slicing (creates new list) numbers = [1, 2, 3, 4, 5] reversed_list = numbers[::-1] print(reversed_list) # [5, 4, 3, 2, 1] # Method 3: reversed() function (returns iterator) numbers = [1, 2, 3, 4, 5] reversed_list = list(reversed(numbers)) print(reversed_list) # [5, 4, 3, 2, 1] # Method 4: loop (manual reversal) numbers = [1, 2, 3, 4, 5] reversed_list = [] for i in range(len(numbers) - 1, -1, -1): reversed_list.append(numbers[i])
[::-1] for simplicity, or reversed() for memory efficiency with large lists.
Answer:
A dictionary is a collection of key-value pairs, while a list is an ordered sequence indexed by integers.
# Dictionary - Key-Value pairs student = { "name": "Alice", "age": 20, "grades": [90, 85, 92] } print(student["name"]) # Access by key: "Alice" print(student.get("age")) # Safe access: 20 # List - Ordered sequence fruits = ["apple", "banana", "cherry"] print(fruits[0]) # Access by index: "apple" # Dictionary operations student["major"] = "Computer Science" # Add new key del student["age"] # Remove key # Iterating through dictionary for key, value in student.items(): print(f"{key}: {value}")
| Feature | List | Dictionary |
|---|---|---|
| Access | By numeric index | By key (any immutable type) |
| Order | Always ordered | Ordered (Python 3.7+) |
| Lookup Speed | O(n) for search | O(1) average for key lookup |
| Use Case | Sequential data | Key-value mappings |
Answer:
List comprehension provides a concise way to create lists based on existing iterables.
# Basic syntax: [expression for item in iterable if condition] # Example 1: Squares of numbers squares = [x**2 for x in range(10)] print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] # Example 2: Filter even numbers numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] evens = [x for x in numbers if x % 2 == 0] print(evens) # [2, 4, 6, 8, 10] # Example 3: String manipulation words = ["hello", "world", "python"] uppercase = [w.upper() for w in words] print(uppercase) # ['HELLO', 'WORLD', 'PYTHON'] # Example 4: Nested list comprehension matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] flattened = [num for row in matrix for num in row] print(flattened) # [1, 2, 3, 4, 5, 6, 7, 8, 9] # Example 5: Dictionary comprehension numbers = [1, 2, 3, 4, 5] squared_dict = {x: x**2 for x in numbers} print(squared_dict) # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25} # Example 6: Conditional expression nums = [1, 2, 3, 4, 5] result = ["even" if x % 2 == 0 else "odd" for x in nums] print(result) # ['odd', 'even', 'odd', 'even', 'odd']
Answer:
# remove() - Removes first occurrence of value fruits = ["apple", "banana", "apple", "cherry"] fruits.remove("apple") # Removes first "apple" print(fruits) # ['banana', 'apple', 'cherry'] # pop() - Removes and returns item at index (default: last) numbers = [1, 2, 3, 4, 5] last = numbers.pop() # Removes and returns 5 second = numbers.pop(1) # Removes and returns 2 print(numbers) # [1, 3, 4] print(last, second) # 5 2 # del - Deletes item by index or slice letters = ["a", "b", "c", "d", "e"] del letters[0] # Delete first element print(letters) # ['b', 'c', 'd', 'e'] del letters[1:3] # Delete slice print(letters) # ['b', 'e'] del letters # Delete entire list # print(letters) # NameError: name 'letters' is not defined
| Method | Removes By | Returns Value | Use Case |
|---|---|---|---|
remove(value) |
First matching value | No (None) | When you know the value |
pop(index) |
Index (default: -1) | Yes | When you need the removed value |
del list[index] |
Index or slice | No | When you know the position |
3. Object-Oriented Programming (OOP)
Answer:
The four pillars of Object-Oriented Programming are:
1. Encapsulation
Bundling data and methods that work on that data within a single unit (class).
class BankAccount: def __init__(self, balance): self.__balance = balance # Private attribute def deposit(self, amount): if amount > 0: self.__balance += amount def get_balance(self): return self.__balance account = BankAccount(1000) account.deposit(500) print(account.get_balance()) # 1500 # print(account.__balance) # AttributeError (private)
2. Inheritance
Creating new classes based on existing classes.
class Animal: def __init__(self, name): self.name = name def speak(self): pass class Dog(Animal): def speak(self): return f"{self.name} says Woof!" class Cat(Animal): def speak(self): return f"{self.name} says Meow!" dog = Dog("Buddy") cat = Cat("Whiskers") print(dog.speak()) # Buddy says Woof! print(cat.speak()) # Whiskers says Meow!
3. Polymorphism
Same interface, different implementations.
class Shape: def area(self): pass class Rectangle(Shape): def __init__(self, width, height): self.width = width self.height = height def area(self): return self.width * self.height class Circle(Shape): def __init__(self, radius): self.radius = radius def area(self): return 3.14159 * self.radius ** 2 shapes = [Rectangle(5, 10), Circle(7)] for shape in shapes: print(f"Area: {shape.area()}")
4. Abstraction
Hiding complex implementation details, showing only essential features.
from abc import ABC, abstractmethod class PaymentProcessor(ABC): @abstractmethod def process_payment(self, amount): pass class CreditCardProcessor(PaymentProcessor): def process_payment(self, amount): print(f"Processing ${amount} via Credit Card") class PayPalProcessor(PaymentProcessor): def process_payment(self, amount): print(f"Processing ${amount} via PayPal") processor = CreditCardProcessor() processor.process_payment(100) # Processing $100 via Credit Card
Answer:
__new__ creates the instance, __init__ initializes it.
class MyClass: def __new__(cls, *args, **kwargs): print("__new__ called - creates instance") instance = super().__new__(cls) return instance def __init__(self, value): print("__init__ called - initializes instance") self.value = value obj = MyClass(42) # Output: # __new__ called - creates instance # __init__ called - initializes instance # Practical use case: Singleton pattern class Singleton: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance s1 = Singleton() s2 = Singleton() print(s1 is s2) # True (same instance)
4. Functions & Decorators
Answer:
*args allows passing variable number of positional arguments, **kwargs allows variable number of keyword arguments.
# *args - Variable positional arguments (tuple) def sum_all(*args): return sum(args) print(sum_all(1, 2, 3)) # 6 print(sum_all(1, 2, 3, 4, 5)) # 15 # **kwargs - Variable keyword arguments (dictionary) def print_info(**kwargs): for key, value in kwargs.items(): print(f"{key}: {value}") print_info(name="Alice", age=25, city="New York") # Output: # name: Alice # age: 25 # city: New York # Combining all parameter types def complete_function(req1, req2, *args, default=10, **kwargs): print(f"Required: {req1}, {req2}") print(f"Args: {args}") print(f"Default: {default}") print(f"Kwargs: {kwargs}") complete_function(1, 2, 3, 4, default=20, extra="value")
Answer:
A decorator is a function that modifies the behavior of another function without changing its code.
import time from functools import wraps # Simple decorator def timer(func): @wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f"{func.__name__} took {end - start:.4f}s") return result return wrapper @timer def slow_function(): time.sleep(1) return "Done!" result = slow_function() # slow_function took 1.0001s # Decorator with arguments def repeat(times): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for _ in range(times): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(times=3) def greet(name): print(f"Hello, {name}!") greet("Alice") # Output: # Hello, Alice! # Hello, Alice! # Hello, Alice!
Answer:
Lambda functions are small anonymous functions defined with the lambda keyword. Use them for simple, one-line operations.
# Basic lambda syntax: lambda arguments: expression # Example 1: Simple operation square = lambda x: x ** 2 print(square(5)) # 25 # Example 2: Multiple arguments add = lambda x, y: x + y print(add(3, 7)) # 10 # Example 3: With map() numbers = [1, 2, 3, 4, 5] squared = list(map(lambda x: x**2, numbers)) print(squared) # [1, 4, 9, 16, 25] # Example 4: With filter() numbers = [1, 2, 3, 4, 5, 6] evens = list(filter(lambda x: x % 2 == 0, numbers)) print(evens) # [2, 4, 6] # Example 5: Sorting with key students = [ {"name": "Alice", "grade": 85}, {"name": "Bob", "grade": 92}, {"name": "Charlie", "grade": 78} ] sorted_students = sorted(students, key=lambda s: s["grade"], reverse=True) print(sorted_students[0]["name"]) # Bob
5. Advanced Concepts
Answer:
The GIL is a mutex that protects access to Python objects, preventing multiple threads from executing Python bytecode simultaneously. This means Python threading is limited for CPU-bound tasks but fine for I/O-bound tasks.
import threading import time # CPU-bound task (GIL limits performance) def cpu_intensive(): count = 0 for _ in range(10000000): count += 1 # Threading doesn't help much for CPU tasks start = time.time() threads = [threading.Thread(target=cpu_intensive) for _ in range(2)] for t in threads: t.start() for t in threads: t.join() print(f"Threading: {time.time() - start:.2f}s") # Multiprocessing bypasses GIL from multiprocessing import Process start = time.time() processes = [Process(target=cpu_intensive) for _ in range(2)] for p in processes: p.start() for p in processes: p.join() print(f"Multiprocessing: {time.time() - start:.2f}s")
multiprocessing to bypass the GIL. For I/O-bound tasks (network, file operations), threading works fine.
Answer:
Generators are functions that use yield instead of return. They produce values lazily (one at a time) rather than computing everything at once.
# Regular function - Returns all at once def get_squares_list(n): result = [] for i in range(n): result.append(i ** 2) return result # Entire list in memory # Generator - Yields one at a time def get_squares_generator(n): for i in range(n): yield i ** 2 # One value at a time # Memory efficient for large datasets gen = get_squares_generator(1000000) print(next(gen)) # 0 print(next(gen)) # 1 print(next(gen)) # 4 # Generator expression (like list comprehension) squares_gen = (x**2 for x in range(10)) # Practical example: Reading large files def read_large_file(file_path): with open(file_path) as file: for line in file: yield line.strip() # Process one line at a time without loading entire file for line in read_large_file("huge_file.txt"): process(line) # Memory efficient
Answer:
Context managers handle resource setup and cleanup automatically using the with statement.
# Built-in context manager: file handling with open("file.txt", "r") as file: content = file.read() # File automatically closed after the block # Creating context manager - Method 1: Class-based class Timer: def __enter__(self): self.start = time.time() return self def __exit__(self, *args): self.end = time.time() print(f"Elapsed: {self.end - self.start:.2f}s") with Timer(): time.sleep(1) # Elapsed: 1.00s # Creating context manager - Method 2: Using contextlib from contextlib import contextmanager @contextmanager def database_connection(): # Setup connection = create_connection() try: yield connection # Provide resource finally: # Cleanup (always executes) connection.close() with database_connection() as conn: conn.execute("SELECT * FROM users") # Connection automatically closed
Answer:
Python uses automatic memory management with reference counting and a cyclic garbage collector.
import sys import gc # Reference counting a = [1, 2, 3] print(sys.getrefcount(a)) # 2 (variable + getrefcount parameter) b = a # Create another reference print(sys.getrefcount(a)) # 3 (increased) del b # Remove reference print(sys.getrefcount(a)) # 2 (decreased) # Circular references (handled by garbage collector) class Node: def __init__(self, value): self.value = value self.next = None node1 = Node(1) node2 = Node(2) node1.next = node2 node2.next = node1 # Circular reference del node1, node2 # Garbage collector will clean up # Manual garbage collection gc.collect() # Force garbage collection print(gc.get_count()) # Get collection statistics
- Reference counting: Immediate cleanup when count reaches zero
- Cyclic GC: Handles circular references
- Generation-based: Objects are tracked in 3 generations
- Most cleanup is automatic; manual intervention rarely needed
6. Algorithms & Problem Solving
Answer:
# Method 1: Using set (find if duplicates exist) def has_duplicates(lst): return len(lst) != len(set(lst)) numbers = [1, 2, 3, 2, 4] print(has_duplicates(numbers)) # True # Method 2: Find which items are duplicates def find_duplicates(lst): seen = set() duplicates = set() for item in lst: if item in seen: duplicates.add(item) else: seen.add(item) return list(duplicates) numbers = [1, 2, 3, 2, 4, 3, 5] print(find_duplicates(numbers)) # [2, 3] # Method 3: Count occurrences from collections import Counter numbers = [1, 2, 3, 2, 4, 3, 5] counter = Counter(numbers) duplicates = [item for item, count in counter.items() if count > 1] print(duplicates) # [2, 3]
Answer:
# Method 1: Using slicing def is_palindrome_v1(s): s = s.lower().replace(" ", "") return s == s[::-1] print(is_palindrome_v1("racecar")) # True print(is_palindrome_v1("A man a plan a canal Panama")) # False (spaces) # Method 2: Two-pointer approach def is_palindrome_v2(s): # Remove non-alphanumeric and convert to lowercase cleaned = "".join(c.lower() for c in s if c.isalnum()) left, right = 0, len(cleaned) - 1 while left < right: if cleaned[left] != cleaned[right]: return False left += 1 right -= 1 return True print(is_palindrome_v2("A man, a plan, a canal: Panama")) # True print(is_palindrome_v2("race a car")) # False # Method 3: Recursive def is_palindrome_recursive(s): s = s.lower().replace(" ", "") if len(s) <= 1: return True if s[0] != s[-1]: return False return is_palindrome_recursive(s[1:-1]) print(is_palindrome_recursive("radar")) # True
Answer:
Print numbers 1-100, but for multiples of 3 print "Fizz", multiples of 5 print "Buzz", and multiples of both print "FizzBuzz".
# Method 1: Traditional if-elif def fizzbuzz_v1(n): for i in range(1, n + 1): if i % 15 == 0: print("FizzBuzz") elif i % 3 == 0: print("Fizz") elif i % 5 == 0: print("Buzz") else: print(i) # Method 2: String concatenation (cleaner) def fizzbuzz_v2(n): for i in range(1, n + 1): output = "" if i % 3 == 0: output += "Fizz" if i % 5 == 0: output += "Buzz" print(output or i) # Method 3: List comprehension (returns list) def fizzbuzz_v3(n): return [ "FizzBuzz" if i % 15 == 0 else "Fizz" if i % 3 == 0 else "Buzz" if i % 5 == 0 else i for i in range(1, n + 1) ] result = fizzbuzz_v3(15) print(result)
7. Web Development with Python
Answer:
| Feature | Flask | Django |
|---|---|---|
| Type | Micro-framework | Full-stack framework |
| Philosophy | Minimalist, flexible | Batteries-included |
| Learning Curve | Easier, simpler | Steeper, more complex |
| ORM | Optional (SQLAlchemy) | Built-in Django ORM |
| Admin Panel | Not included | Automatic admin interface |
| Best For | Small apps, APIs, microservices | Large applications, complex projects |
# Flask - Minimal app from flask import Flask app = Flask(__name__) @app.route("/") def home(): return "Hello, Flask!" if __name__ == "__main__": app.run() # Django - View function from django.http import HttpResponse def home(request): return HttpResponse("Hello, Django!")
Answer:
from flask import Flask, jsonify, request app = Flask(__name__) # In-memory data store users = [ {"id": 1, "name": "Alice", "email": "alice@example.com"}, {"id": 2, "name": "Bob", "email": "bob@example.com"} ] # GET - Retrieve all users @app.route("/api/users", methods=["GET"]) def get_users(): return jsonify(users), 200 # GET - Retrieve single user @app.route("/api/users/<int:user_id>", methods=["GET"]) def get_user(user_id): user = next((u for u in users if u["id"] == user_id), None) if user: return jsonify(user), 200 return jsonify({"error": "User not found"}), 404 # POST - Create new user @app.route("/api/users", methods=["POST"]) def create_user(): data = request.get_json() new_user = { "id": max(u["id"] for u in users) + 1, "name": data.get("name"), "email": data.get("email") } users.append(new_user) return jsonify(new_user), 201 # PUT - Update user @app.route("/api/users/<int:user_id>", methods=["PUT"]) def update_user(user_id): user = next((u for u in users if u["id"] == user_id), None) if not user: return jsonify({"error": "User not found"}), 404 data = request.get_json() user["name"] = data.get("name", user["name"]) user["email"] = data.get("email", user["email"]) return jsonify(user), 200 # DELETE - Remove user @app.route("/api/users/<int:user_id>", methods=["DELETE"]) def delete_user(user_id): global users users = [u for u in users if u["id"] != user_id] return jsonify({"message": "User deleted"}), 200 if __name__ == "__main__": app.run(debug=True)
8. Database & SQL
Answer:
# SQLite (built-in) import sqlite3 # Connect to database conn = sqlite3.connect("example.db") cursor = conn.cursor() # Create table cursor.execute(""" CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE ) """) # Insert data cursor.execute("INSERT INTO users (name, email) VALUES (?, ?)", ("Alice", "alice@example.com")) conn.commit() # Query data cursor.execute("SELECT * FROM users") users = cursor.fetchall() for user in users: print(user) # Close connection conn.close() # Using context manager (better) with sqlite3.connect("example.db") as conn: cursor = conn.cursor() cursor.execute("SELECT * FROM users WHERE name = ?", ("Alice",)) result = cursor.fetchone() print(result) # PostgreSQL (requires psycopg2) import psycopg2 conn = psycopg2.connect( host="localhost", database="mydb", user="myuser", password="mypassword" ) # MySQL (requires mysql-connector-python) import mysql.connector conn = mysql.connector.connect( host="localhost", user="myuser", password="mypassword", database="mydb" )
9. Testing & Debugging
Answer:
# Using unittest (built-in) import unittest class Calculator: def add(self, a, b): return a + b def divide(self, a, b): if b == 0: raise ValueError("Cannot divide by zero") return a / b class TestCalculator(unittest.TestCase): def setUp(self): self.calc = Calculator() def test_add(self): result = self.calc.add(2, 3) self.assertEqual(result, 5) def test_divide(self): result = self.calc.divide(10, 2) self.assertEqual(result, 5) def test_divide_by_zero(self): with self.assertRaises(ValueError): self.calc.divide(10, 0) if __name__ == "__main__": unittest.main() # Using pytest (more popular) import pytest def test_add(): calc = Calculator() assert calc.add(2, 3) == 5 def test_divide_by_zero(): calc = Calculator() with pytest.raises(ValueError): calc.divide(10, 0)
10. Interview Tips & Strategies
📚 Before the Interview
- Review Python fundamentals thoroughly
- Practice on LeetCode, HackerRank
- Study common data structures and algorithms
- Review your past projects
- Prepare questions to ask the interviewer
💬 During the Interview
- Think out loud - explain your reasoning
- Ask clarifying questions
- Start with a simple solution, then optimize
- Test your code with examples
- Discuss time and space complexity
✍️ Coding Best Practices
- Write clean, readable code
- Use meaningful variable names
- Follow PEP 8 style guidelines
- Handle edge cases
- Comment complex logic
🎯 Common Topics to Master
- Data structures (lists, dicts, sets)
- String manipulation
- Recursion and iteration
- OOP concepts
- Exception handling
- File I/O
- LeetCode: Practice coding problems with Python
- HackerRank: Python challenges and competitions
- Real Python: Tutorials and interview guides
- "Cracking the Coding Interview": Classic interview prep book
- Python Official Docs: Always refer to official documentation
Behavioral Interview Questions
Don't forget to prepare for behavioral questions:
- "Tell me about a challenging project you worked on"
- "How do you handle debugging complex issues?"
- "Describe a time you had to learn a new technology quickly"
- "How do you stay updated with Python and programming trends?"
- "Tell me about a time you collaborated with a team"