🎯 Python Interview Questions & Answers

Master Python interviews with 50+ comprehensive questions covering fundamentals, data structures, OOP, and advanced topics

📚 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)

Q1: What is Python? What are its key features? Beginner

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
Q2: What is the difference between Python 2 and Python 3? Beginner

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:
Q3: What are Python's built-in data types? Beginner

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
Q4: What is the difference between list and tuple? Beginner

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"
}
Q5: Explain Python's indentation rules and why they matter. Beginner

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
Best Practice: Use 4 spaces per indentation level (PEP 8 standard). Most editors can be configured to convert tabs to spaces automatically.

2. Data Structures

Q6: How do you reverse a list in Python? Intermediate

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])
Best Choice: Use slicing [::-1] for simplicity, or reversed() for memory efficiency with large lists.
Q7: What is a dictionary and how is it different from a list? Beginner

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
Q8: Explain list comprehension with examples. Intermediate

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']
Q9: What is the difference between remove(), pop(), and del for lists? Beginner

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)

Q10: Explain the four pillars of OOP. Intermediate

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
Q11: What is the difference between __init__ and __new__? Advanced

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

Q12: What are *args and **kwargs? Intermediate

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")
Q13: What is a decorator? Write a simple decorator example. Intermediate

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!
Q14: What is a lambda function? When should you use it? Beginner

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
When NOT to use lambda: If your function logic is complex or requires multiple statements, use a regular function instead for better readability.

5. Advanced Concepts

Q15: Explain Python's Global Interpreter Lock (GIL). Advanced

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")
Solution: For CPU-intensive tasks, use multiprocessing to bypass the GIL. For I/O-bound tasks (network, file operations), threading works fine.
Q16: What are generators? How do they differ from regular functions? Intermediate

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
Q17: What is a context manager? How do you create one? Intermediate

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
Q18: Explain Python's memory management and garbage collection. Advanced

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
Key Points:
  • 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

Q19: How do you find duplicates in a list? Beginner

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]
Q20: Write a function to check if a string is a palindrome. Beginner

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
Q21: Implement FizzBuzz. Beginner

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

Q22: What is the difference between Flask and Django? Intermediate

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!")
Q23: How do you create a REST API endpoint in Flask? Intermediate

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

Q24: How do you connect to a database in Python? Intermediate

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

Q25: How do you write unit tests in Python? Intermediate

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
Remember: Interviewers are looking for problem-solving skills, not just knowledge. Show your thought process, communicate clearly, and don't be afraid to ask questions. It's better to ask for clarification than to solve the wrong problem!
Additional Resources:
  • 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"