InterviewPitch
Python & Algorithms

Python Interview Questions with Answers

Most Asked Python Interview Questions for Software Engineer Roles

100+ QuestionsDetailed AnswersCode ExamplesUpdated for 2026

Introduction

This page provides a complete collection of Python Interview Questions and Answers designed for Python developers, software engineers, data scientists, automation engineers, and candidates preparing for technical interviews. Python is a high-level, interpreted, and general-purpose programming language known for its simple syntax, powerful libraries, and wide range of applications including web development, automation, artificial intelligence, machine learning, data science, and backend development. This interview guide covers beginner, intermediate, and advanced Python concepts including Python fundamentals, OOP concepts, functions, decorators, generators, exception handling, collections, file handling, multithreading, multiprocessing, Django, Flask, APIs, machine learning concepts, and real-world coding interview problems.

Why Python?

  • Simple, readable syntax that accelerates development
  • Vast ecosystem of libraries for every domain (NumPy, Pandas, Django, TensorFlow, etc.)
  • Highly versatile – from web development to AI and data science
  • Strong community support and extensive documentation
  • Widely used in industry, making it a top skill for technical interviews

Most Asked Python Interview Questions

Beginner

1. What is Python? What are the benefits of using Python?

Python is a high-level, interpreted, and general-purpose programming language designed to be simple, readable, and easy to learn. It was created by Guido van Rossum and released in 1991.

Python emphasizes code readability and allows developers to write programs with fewer lines of code compared to languages like C++ or Java. It supports multiple programming paradigms:

  • Object-Oriented Programming (OOP)
  • Procedural Programming
  • Functional Programming

Python is widely used in web development, data science, artificial intelligence, automation, and software development.

Benefits of Using Python

  1. Easy to Learn and Use: Simple and readable syntax makes it beginner-friendly.
  2. Interpreted Language: Executed line-by-line, making debugging easier.
  3. Platform Independent: Runs on Windows, Linux, and macOS without changes.
  4. Used in Modern Technologies
    • Artificial Intelligence & Machine Learning
    • Data Science & Analytics
    • Web Development
    • Automation & Scripting
Beginner
2. Is Python a compiled language or an interpreted language?

Python is both compiled and interpreted, but it is commonly referred to as an interpreted language.

Compilation Step

When you run a Python program, the source code (.py file) is first compiled into bytecode (.pyc files). This step happens automatically and is not visible to the user.

Interpretation Step

The generated bytecode is then executed by the Python Virtual Machine (PVM) line by line.

How Python Works Internally
  1. You write code → example.py
  2. Python compiles it → Bytecode (.pyc)
  3. Python Virtual Machine executes it
Beginner
3. What is a dynamically typed language?

A dynamically typed language is a programming language where the type of a variable is determined at runtime.

  • You don't need to declare the data type.
  • The language decides it during execution.
python
x = 10        # x is an integer
x = "Hello"   # now x becomes a string
Beginner
4. What is the importance of indentation in Python?

In Python, indentation is not just for readability — it is a mandatory part of the syntax used to define blocks of code.

Unlike other languages that use curly braces {}, Python uses indentation (spaces or tabs) to group statements together.

  • Defines code blocks for loops, functions, and conditionals
  • Inconsistent indentation raises an IndentationError
  • Standard convention is 4 spaces per level
python
# Python indentation example
if True:
    print("Inside block")
    print("Still inside")
print("Outside block")
Beginner
5. What are Python's built-in data types?

Python provides several built-in data types to store different kinds of values.

  • int — Integer numbers
  • float — Decimal numbers
  • str — Text / String
  • bool — True or False
  • list — Ordered, mutable collection
  • tuple — Ordered, immutable collection
  • dict — Key-value pairs
  • set — Unordered unique elements
python
# Python data types
x = 10          # int
y = 3.14        # float
name = "Python" # str
flag = True     # bool
nums = [1,2,3]  # list
info = {"a":1}  # dict
t = (1, 2)      # tuple
s = {1, 2, 3}   # set
Beginner
6. What is the difference between mutable and immutable objects?

Mutable objects can be changed after creation. Immutable objects cannot be modified once created.

  • Mutable: list, dict, set
  • Immutable: int, float, str, tuple, bool

Immutable objects are hashable and can be used as dictionary keys, while mutable objects cannot.

python
# Mutable vs Immutable
# Mutable
my_list = [1, 2, 3]
my_list[0] = 99
print(my_list)   # [99, 2, 3]

# Immutable
my_tuple = (1, 2, 3)
# my_tuple[0] = 99  # TypeError
Beginner
7. What is the difference between a List and a Tuple?

Both List and Tuple are ordered sequences in Python, but they differ in mutability.

  • List is mutable — elements can be added, removed, or changed
  • Tuple is immutable — once created, it cannot be changed
  • Tuples are faster and use less memory than lists
  • Tuples can be used as dictionary keys; lists cannot
python
# List vs Tuple
my_list = [1, 2, 3]    # mutable
my_tuple = (1, 2, 3)   # immutable

my_list.append(4)
print(my_list)    # [1, 2, 3, 4]

# my_tuple.append(4)  # AttributeError
Beginner
8. What is a Dictionary in Python?

A Dictionary is an unordered collection of key-value pairs. Each key must be unique and immutable.

Dictionaries are used for fast lookups, counting frequencies, and representing structured data.

  • Keys must be immutable (str, int, tuple)
  • Values can be any type
  • Average O(1) lookup, insert, delete
  • Ordered by insertion order since Python 3.7
python
# Dictionary example
student = {
    "name": "Alice",
    "age": 22,
    "grade": "A"
}

print(student["name"])   # Alice
student["age"] = 23
print(student)
Beginner
9. What is List Comprehension?

List Comprehension provides a concise way to create lists from existing iterables in a single line.

It is more readable and typically faster than using a traditional for loop to build a list.

  • Syntax: [expression for item in iterable if condition]
  • Can include optional filtering conditions
  • More Pythonic than using map() and filter()
  • Can be nested for multi-dimensional data
python
# List comprehension
squares = [x**2 for x in range(1, 6)]
print(squares)   # [1, 4, 9, 16, 25]

evens = [x for x in range(10) if x % 2 == 0]
print(evens)     # [0, 2, 4, 6, 8]
Beginner
10. What is a Lambda Function?

A Lambda function is an anonymous, single-expression function defined using the lambda keyword.

Lambda functions are useful for short, throwaway functions especially as arguments to higher-order functions.

  • Syntax: lambda arguments: expression
  • Can take multiple arguments
  • Returns the value of the expression automatically
  • Commonly used with map(), filter(), sorted()
python
# Lambda function
square = lambda x: x ** 2
print(square(5))   # 25

add = lambda a, b: a + b
print(add(3, 4))   # 7
Beginner
11. What are *args and **kwargs in Python?

*args allows a function to accept any number of positional arguments as a tuple.

**kwargs allows a function to accept any number of keyword arguments as a dictionary.

  • *args collects extra positional arguments
  • **kwargs collects extra keyword arguments
  • Both can be combined in the same function
  • Useful for flexible and generic function signatures
python
# *args and **kwargs
def greet(*args):
    for name in args:
        print(f"Hello, {name}!")

greet("Alice", "Bob", "Charlie")

def show_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

show_info(name="Alice", age=22)
Intermediate
12. What is Object-Oriented Programming in Python?

OOP is a programming paradigm that organizes code around objects and classes rather than functions and procedures.

Python supports OOP with classes, objects, inheritance, encapsulation, and polymorphism.

  • Class — blueprint for creating objects
  • Object — instance of a class
  • __init__ — constructor method
  • self — reference to the current instance
python
# OOP - Class and Object
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        print(f"{self.name} makes a sound")

dog = Animal("Dog")
dog.speak()
Intermediate
13. What is Inheritance in Python?

Inheritance allows a child class to inherit attributes and methods from a parent class, enabling code reuse.

Python supports single, multiple, multi-level, and hierarchical inheritance.

  • Child class extends parent class
  • Use super() to call parent methods
  • Method overriding allows customizing behavior
  • Promotes the DRY (Don't Repeat Yourself) principle
python
# Inheritance
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        print(f"{self.name} makes a sound")

class Dog(Animal):
    def speak(self):
        print(f"{self.name} says Woof!")

d = Dog("Buddy")
d.speak()
Intermediate
14. What is Encapsulation in Python?

Encapsulation is the concept of hiding the internal details of an object and restricting direct access to its data.

In Python, it is achieved using private attributes (prefixed with __) and public getter/setter methods.

  • Single underscore _var — convention for protected
  • Double underscore __var — name mangling (private)
  • Use getters and setters to control access
  • Protects data integrity
python
# Encapsulation
class BankAccount:
    def __init__(self, balance):
        self.__balance = balance   # private

    def get_balance(self):
        return self.__balance

    def deposit(self, amount):
        self.__balance += amount

acc = BankAccount(1000)
acc.deposit(500)
print(acc.get_balance())   # 1500
Intermediate
15. What is Polymorphism in Python?

Polymorphism means "many forms" — the same method name behaves differently based on the object calling it.

Python achieves polymorphism through method overriding in subclasses and duck typing.

  • Same interface, different implementations
  • Enables writing generic code
  • Supports duck typing ("if it quacks like a duck...")
  • Used heavily in Python's built-in functions like len()
python
# Polymorphism
class Cat:
    def speak(self):
        return "Meow"

class Dog:
    def speak(self):
        return "Woof"

animals = [Cat(), Dog()]
for a in animals:
    print(a.speak())
Intermediate
16. What is Abstraction in Python?

Abstraction hides complex implementation details and only exposes what is necessary to the user.

In Python, abstraction is achieved using Abstract Base Classes (ABC) from the abc module.

  • Abstract methods must be implemented by subclasses
  • Cannot instantiate an abstract class directly
  • Defines a contract for subclasses
  • Improves code maintainability
python
# Abstraction
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Circle(Shape):
    def __init__(self, r):
        self.r = r

    def area(self):
        return 3.14 * self.r ** 2

c = Circle(5)
print(c.area())
Intermediate
17. What are Decorators in Python?

A Decorator is a function that takes another function as input, adds extra behavior, and returns it.

Decorators use the @ syntax and are commonly used for logging, authentication, caching, and timing.

  • Functions are first-class objects in Python
  • Decorators wrap functions without modifying their code
  • Can be stacked (multiple decorators)
  • Built-in decorators: @staticmethod, @classmethod, @property
python
# Decorators
def my_decorator(func):
    def wrapper():
        print("Before function")
        func()
        print("After function")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()
Intermediate
18. What are Generators in Python?

A Generator is a function that uses yield to return values one at a time, pausing execution between each yield.

Generators are memory-efficient because they produce items lazily on demand instead of storing the entire sequence.

  • Uses yield instead of return
  • Returns a generator object (iterator)
  • Ideal for large data streams
  • Supports generator expressions like list comprehensions
python
# Generators
def count_up(n):
    i = 1
    while i <= n:
        yield i
        i += 1

gen = count_up(5)
for num in gen:
    print(num)
Intermediate
19. What is Exception Handling in Python?

Exception Handling allows programs to gracefully handle runtime errors instead of crashing.

Python uses try, except, else, and finally blocks for error management.

  • try — code that might raise an exception
  • except — handles specific or general exceptions
  • else — runs if no exception occurred
  • finally — always runs (cleanup code)
python
# Exception Handling
try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"Error: {e}")
except ValueError as e:
    print(f"Value Error: {e}")
finally:
    print("This always runs")
Intermediate
20. What is File Handling in Python?

Python provides built-in functions to create, read, write, and close files using the open() function.

The with statement (context manager) is the recommended way to handle files as it ensures automatic closing.

  • "r" — read mode
  • "w" — write mode (overwrites)
  • "a" — append mode
  • "rb" / "wb" — binary modes
python
# File Handling
# Write to file
with open("test.txt", "w") as f:
    f.write("Hello, Python!")

# Read from file
with open("test.txt", "r") as f:
    content = f.read()
    print(content)
Intermediate
21. What are map(), filter(), and reduce() in Python?

These are higher-order functions that operate on iterables and are core to functional programming in Python.

  • map() — applies a function to every element
  • filter() — returns elements that satisfy a condition
  • reduce() — reduces a sequence to a single value

reduce() requires importing from functools in Python 3.

python
# Map, Filter, Reduce
from functools import reduce

nums = [1, 2, 3, 4, 5]

doubled = list(map(lambda x: x * 2, nums))
print(doubled)    # [2, 4, 6, 8, 10]

evens = list(filter(lambda x: x % 2 == 0, nums))
print(evens)      # [2, 4]

total = reduce(lambda a, b: a + b, nums)
print(total)      # 15
Intermediate
22. What are Iterators in Python?

An Iterator is an object that implements the __iter__() and __next__() methods to traverse elements one at a time.

All iterators are also iterables, but not all iterables are iterators.

  • __iter__ returns the iterator object itself
  • __next__ returns the next element
  • Raises StopIteration when exhausted
  • Generators are a simple way to create iterators
python
# Iterators
class Counter:
    def __init__(self, low, high):
        self.low = low
        self.high = high

    def __iter__(self):
        return self

    def __next__(self):
        if self.low > self.high:
            raise StopIteration
        val = self.low
        self.low += 1
        return val

for num in Counter(1, 5):
    print(num)
Intermediate
23. What is the difference between Shallow Copy and Deep Copy?

A Shallow Copy creates a new object but references the same nested objects as the original.

A Deep Copy creates a completely independent clone of the original object and all nested objects.

  • copy.copy() — shallow copy
  • copy.deepcopy() — deep copy
  • Shallow copy is faster but shares inner references
  • Deep copy is slower but fully independent
python
# Shallow vs Deep Copy
import copy

original = [[1, 2], [3, 4]]

shallow = copy.copy(original)
deep = copy.deepcopy(original)

original[0][0] = 99

print(shallow)   # [[99, 2], [3, 4]] - affected
print(deep)      # [[1, 2], [3, 4]]  - not affected
Intermediate
24. What are Global and Local Variables?

A Local variable is defined inside a function and accessible only within that function.

A Global variable is defined outside any function and accessible throughout the module.

  • Use global keyword to modify a global variable inside a function
  • Use nonlocal keyword to modify enclosing scope variable in nested functions
  • Local scope takes priority over global scope
  • Avoid excessive global variables for cleaner code
python
# Global and Local Variables
x = "global"

def my_func():
    x = "local"
    print(x)   # local

my_func()
print(x)       # global

def modify_global():
    global x
    x = "modified"

modify_global()
print(x)       # modified
Intermediate
25. What is Slicing in Python?

Slicing extracts a portion of a sequence (list, string, tuple) using the syntax [start:stop:step].

It is a powerful and concise way to work with subsequences without modifying the original.

  • start — inclusive start index (default 0)
  • stop — exclusive end index (default end)
  • step — increment between elements
  • Negative step reverses the sequence
python
# Slicing
my_list = [0, 1, 2, 3, 4, 5]

print(my_list[1:4])    # [1, 2, 3]
print(my_list[:3])     # [0, 1, 2]
print(my_list[3:])     # [3, 4, 5]
print(my_list[::2])    # [0, 2, 4]
print(my_list[::-1])   # [5, 4, 3, 2, 1, 0]
Beginner
26. What are important String Methods in Python?

Python strings are immutable sequences of characters with many built-in methods for manipulation and analysis.

  • strip() — removes leading/trailing whitespace
  • split() — splits string into a list
  • join() — joins list elements into a string
  • replace() — replaces substrings
  • find() / index() — search for substring
  • startswith() / endswith() — prefix/suffix check
python
# String Methods
s = "  Hello, Python!  "

print(s.strip())          # "Hello, Python!"
print(s.lower())          # "  hello, python!  "
print(s.upper())          # "  HELLO, PYTHON!  "
print(s.replace("Python", "World"))
print(s.split(","))       # ['  Hello', ' Python!  ']
print("Python" in s)      # True
Beginner
27. What are f-strings in Python?

f-strings (formatted string literals) are the modern way to embed expressions inside strings using f"..." syntax.

Introduced in Python 3.6, they are faster and more readable than format() or % formatting.

  • Prefix string with f or F
  • Embed expressions inside {}
  • Support format specifiers like :.2f
  • Can call functions inside the braces
python
# f-strings
name = "Alice"
age = 22

print(f"Name: {name}, Age: {age}")
print(f"5 + 3 = {5 + 3}")
print(f"Pi is approximately {3.14159:.2f}")
Beginner
28. What are enumerate() and zip() in Python?

enumerate() adds a counter to an iterable and returns it as an enumerate object with index-value pairs.

zip() combines multiple iterables element-by-element into tuples, stopping at the shortest iterable.

  • enumerate() is great for loops needing both index and value
  • zip() is great for parallel iteration
  • Both return lazy iterators
  • zip() can be used to unzip with zip(*pairs)
python
# enumerate and zip
fruits = ["apple", "banana", "cherry"]

for i, fruit in enumerate(fruits, start=1):
    print(f"{i}. {fruit}")

names = ["Alice", "Bob"]
scores = [95, 87]

for name, score in zip(names, scores):
    print(f"{name}: {score}")
Intermediate
29. What is Dictionary Comprehension?

Dictionary Comprehension provides a concise way to create dictionaries from iterables in a single expression.

It follows the same pattern as list comprehension but uses curly braces with key-value pairs.

  • Syntax: {key: value for item in iterable if condition}
  • More concise than using a loop with dict.update()
  • Can transform and filter existing dictionaries
  • More Pythonic way to build dictionaries
python
# Dictionary Comprehension
squares = {x: x**2 for x in range(1, 6)}
print(squares)
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

filtered = {k: v for k, v in squares.items() if v > 5}
print(filtered)
# {3: 9, 4: 16, 5: 25}
Intermediate
30. What are Set Operations in Python?

Python Sets are unordered collections of unique elements that support mathematical set operations.

Sets are optimized for membership testing and eliminating duplicates efficiently.

  • | or union() — all elements from both sets
  • & or intersection() — common elements
  • - or difference() — elements in first but not second
  • ^ or symmetric_difference() — elements in either but not both
python
# Set Operations
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

print(a | b)   # Union:        {1,2,3,4,5,6}
print(a & b)   # Intersection: {3, 4}
print(a - b)   # Difference:   {1, 2}
print(a ^ b)   # Symmetric:    {1,2,5,6}
Intermediate
31. What is Recursion in Python?

Recursion is when a function calls itself to solve a smaller version of the same problem.

Every recursive function needs a base case to prevent infinite recursion. Python has a default recursion limit of 1000.

  • Base case — stops the recursion
  • Recursive case — reduces the problem
  • Python default limit: sys.setrecursionlimit()
  • Can be replaced by iterative + stack for large inputs
python
# Recursion
def factorial(n):
    if n == 0:
        return 1
    return n * factorial(n - 1)

print(factorial(5))   # 120

def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(7))   # 13
Intermediate
32. How does Sorting work in Python?

Python provides two main sorting methods: sorted() (returns a new list) and .sort() (sorts in-place).

Both use the Timsort algorithm with O(n log n) time complexity and support custom sort keys.

  • sorted() — works on any iterable, returns new list
  • .sort() — only for lists, modifies in-place
  • key parameter — custom sorting function
  • reverse=True — descending order
python
# Sorting
nums = [3, 1, 4, 1, 5, 9, 2, 6]

print(sorted(nums))              # ascending
print(sorted(nums, reverse=True)) # descending

students = [("Alice", 85), ("Bob", 92), ("Charlie", 78)]
students.sort(key=lambda x: x[1], reverse=True)
print(students)
Intermediate
33. What is the difference between *args and **kwargs in advanced usage?

In advanced usage, *args and **kwargs can be combined with regular parameters and used for function composition.

They are also used for unpacking arguments when calling functions using the * and ** operators.

  • Order: def f(pos, *args, kw_only, **kwargs)
  • Useful in decorators and wrapper functions
  • Enables highly flexible API design
  • Can unpack with f(*list_args, **dict_kwargs)
python
# *args and **kwargs advanced
def mixed(a, b, *args, **kwargs):
    print(f"a={a}, b={b}")
    print(f"args={args}")
    print(f"kwargs={kwargs}")

mixed(1, 2, 3, 4, 5, name="Alice", age=22)
Advanced
34. What is a Context Manager in Python?

A Context Manager defines a runtime context for executing code, most commonly used with the with statement.

It ensures proper resource management (like file closing or DB connections) using __enter__ and __exit__ methods.

  • __enter__ — called when entering the with block
  • __exit__ — called when leaving the block (even on error)
  • Can also be created using @contextmanager decorator
  • Common use: file handling, locks, database connections
python
# Context Manager
class ManagedFile:
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        self.file = open(self.name, "w")
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()
        print("File closed")

with ManagedFile("demo.txt") as f:
    f.write("Hello from context manager")
Advanced
35. What is the @property Decorator?

The @property decorator allows a method to be accessed like an attribute, enabling controlled access to private data.

It is used to implement getters, setters, and deleters in a Pythonic way without calling methods explicitly.

  • @property — defines a getter
  • @attr.setter — defines a setter with validation
  • @attr.deleter — defines a deleter
  • Enables attribute-style access while maintaining encapsulation
python
# Property Decorator
class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("Too cold!")
        self._celsius = value

    @property
    def fahrenheit(self):
        return self._celsius * 9/5 + 32

t = Temperature(25)
print(t.fahrenheit)   # 77.0
t.celsius = 30
print(t.celsius)      # 30
Advanced
36. What are Class Methods and Static Methods?

A class method receives the class cls as the first argument and can access/modify class state.

A static method receives no implicit first argument and behaves like a regular function inside the class namespace.

  • @classmethod — used for factory methods and class-level logic
  • @staticmethod — used for utility functions related to the class
  • Class methods can modify class variables
  • Static methods cannot access class or instance variables
python
# Class Methods and Static Methods
class MathUtils:
    multiplier = 2

    @classmethod
    def multiply(cls, x):
        return cls.multiplier * x

    @staticmethod
    def add(a, b):
        return a + b

print(MathUtils.multiply(5))   # 10
print(MathUtils.add(3, 4))     # 7
Advanced
37. What are Dunder (Magic) Methods in Python?

Dunder methods (double underscore methods) are special methods that Python calls automatically for built-in operations.

They allow custom objects to emulate built-in types and integrate seamlessly with Python's operators.

  • __init__ — constructor
  • __str__ / __repr__ — string representation
  • __add__, __mul__ — operator overloading
  • __len__, __getitem__ — sequence protocol
python
# Dunder / Magic Methods
class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __str__(self):
        return f"Vector({self.x}, {self.y})"

    def __len__(self):
        return 2

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)   # Vector(4, 6)
print(len(v1))   # 2
Advanced
38. What is Multiple Inheritance and MRO in Python?

Multiple Inheritance allows a class to inherit from more than one parent class simultaneously.

Python uses the MRO (Method Resolution Order) with the C3 linearization algorithm to determine which method is called.

  • MRO defines the search order for methods
  • Use ClassName.__mro__ to view the order
  • The Diamond Problem is resolved by MRO
  • super() follows MRO automatically
python
# Multiple Inheritance
class A:
    def hello(self):
        print("Hello from A")

class B(A):
    def hello(self):
        print("Hello from B")

class C(A):
    def hello(self):
        print("Hello from C")

class D(B, C):
    pass

d = D()
d.hello()   # Hello from B (MRO)
print(D.__mro__)
Advanced
39. What is the Walrus Operator (:=) in Python?

The Walrus Operator (:=) is the assignment expression operator introduced in Python 3.8.

It allows assignment inside expressions, reducing redundant variable evaluations in loops and comprehensions.

  • Assigns and returns value in one expression
  • Useful in while loops to avoid double evaluation
  • Can simplify list comprehensions with intermediate values
  • Should be used sparingly to maintain readability
python
# Walrus Operator :=
import re

data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Without walrus
filtered = [y for x in data if (y := x * 2) > 10]
print(filtered)

# Useful in while loops
while chunk := input("Enter text (empty to stop): "):
    print(f"You entered: {chunk}")
Advanced
40. What are Type Hints in Python?

Type Hints allow you to annotate function parameters and return values with expected types, improving code clarity.

Introduced in Python 3.5, they are not enforced at runtime but are used by tools like mypy for static type checking.

  • Use : for parameter types and -> for return types
  • Import complex types from typing module
  • Improves IDE autocomplete and error detection
  • Checked by tools like mypy, pyright, pylance
python
# Type Hints
def greet(name: str) -> str:
    return f"Hello, {name}!"

def add(a: int, b: int) -> int:
    return a + b

from typing import List, Dict, Optional

def process(items: List[int]) -> Dict[str, int]:
    return {"sum": sum(items), "count": len(items)}

print(greet("Alice"))
print(add(3, 4))
print(process([1, 2, 3, 4]))
Advanced
41. What are Dataclasses in Python?

Dataclasses (introduced in Python 3.7) automatically generate boilerplate code like __init__, __repr__, and __eq__ for classes that primarily store data.

They reduce repetitive code while providing a clean way to define data-centric classes.

  • Use @dataclass decorator
  • Auto-generates __init__, __repr__, __eq__
  • Support default values with field()
  • Can be made immutable with frozen=True
python
# Dataclasses
from dataclasses import dataclass, field

@dataclass
class Student:
    name: str
    age: int
    grades: list = field(default_factory=list)

    def average(self) -> float:
        return sum(self.grades) / len(self.grades) if self.grades else 0

s = Student("Alice", 22, [85, 90, 92])
print(s)
print(s.average())   # 89.0
Intermediate
42. What is a Named Tuple in Python?

A Named Tuple is a subclass of tuple that assigns names to each position, making code more readable and self-documenting.

It combines the immutability of tuples with the readability of attribute access.

  • Access by name: p.x or by index: p[0]
  • Immutable like regular tuples
  • More memory-efficient than a dict
  • Available in collections module
python
# Named Tuple
from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)

print(p.x, p.y)    # 3 4
print(p[0], p[1])  # 3 4

distance = (p.x**2 + p.y**2) ** 0.5
print(f"Distance: {distance}")   # 5.0
Intermediate
43. What is Counter in Python's collections module?

Counter is a subclass of dictionary that counts the occurrences of elements in an iterable.

It is one of the most useful tools for frequency analysis and is commonly used in coding interviews.

  • Returns a dict-like object with element counts
  • most_common(n) — returns top n elements
  • Supports arithmetic operations (+, -, &, |)
  • Available in collections module
python
# Counter
from collections import Counter

words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
count = Counter(words)

print(count)                  # Counter({'apple': 3, ...})
print(count.most_common(2))   # [('apple', 3), ('banana', 2)]
print(count["cherry"])        # 1
Intermediate
44. What is defaultdict in Python?

defaultdict is a subclass of dict that provides a default value for missing keys automatically, avoiding KeyError.

It is very useful for grouping, counting, and building graph adjacency lists.

  • Pass a default factory: int, list, set
  • Accessing a missing key creates it with the default
  • Avoids boilerplate if key not in dict checks
  • Available in collections module
python
# defaultdict
from collections import defaultdict

word_count = defaultdict(int)
sentence = "the cat sat on the mat the cat"

for word in sentence.split():
    word_count[word] += 1

print(dict(word_count))

graph = defaultdict(list)
graph["A"].append("B")
graph["A"].append("C")
print(dict(graph))
Intermediate
45. What is OrderedDict in Python?

OrderedDict is a dictionary subclass that remembers the insertion order of keys (most useful before Python 3.7).

It provides extra methods like move_to_end() which are not available in regular dicts.

  • Maintains insertion order explicitly
  • move_to_end(key) — moves item to front or back
  • Used in LRU Cache implementation
  • Available in collections module
python
# OrderedDict
from collections import OrderedDict

od = OrderedDict()
od["banana"] = 3
od["apple"] = 2
od["cherry"] = 5

for key, value in od.items():
    print(f"{key}: {value}")

od.move_to_end("banana")
print(list(od.keys()))
Advanced
46. What are Regular Expressions in Python?

Regular Expressions (regex) are sequences of characters that define search patterns for string matching and manipulation.

Python's re module provides functions to search, match, find, replace, and split strings using regex patterns.

  • re.search() — find pattern anywhere in string
  • re.findall() — return all matches as list
  • re.sub() — replace pattern with string
  • re.compile() — precompile pattern for reuse
python
# Regular Expressions
import re

text = "My phone is 123-456-7890 and backup is 987-654-3210"

# Find all phone numbers
phones = re.findall(r"d{3}-d{3}-d{4}", text)
print(phones)

# Search
match = re.search(r"d+", text)
print(match.group())   # 123

# Replace
clean = re.sub(r"d{3}-d{3}-d{4}", "XXX-XXX-XXXX", text)
print(clean)
Advanced
47. What is Threading in Python?

Threading allows concurrent execution of multiple threads within the same process, sharing memory space.

Due to the GIL (Global Interpreter Lock), Python threads are best for I/O-bound tasks, not CPU-bound tasks.

  • Use threading.Thread to create threads
  • GIL prevents true parallelism for CPU-bound tasks
  • Great for I/O-bound tasks (network, file operations)
  • Use Lock to prevent race conditions
python
# Threading
import threading
import time

def worker(name, delay):
    print(f"{name} started")
    time.sleep(delay)
    print(f"{name} finished")

t1 = threading.Thread(target=worker, args=("Thread-1", 2))
t2 = threading.Thread(target=worker, args=("Thread-2", 1))

t1.start()
t2.start()

t1.join()
t2.join()
print("All threads done")
Advanced
48. What is Multiprocessing in Python?

Multiprocessing creates separate processes with their own memory space, bypassing the GIL for true parallelism.

It is ideal for CPU-bound tasks like scientific computations and data processing.

  • Each process has its own Python interpreter and memory
  • Bypasses GIL — true parallelism
  • Pool.map() — parallel function execution
  • Higher overhead than threads (separate memory)
python
# Multiprocessing
from multiprocessing import Process, Pool
import os

def square(n):
    return n * n

def show_pid(name):
    print(f"{name}: PID={os.getpid()}")

if __name__ == "__main__":
    with Pool(4) as p:
        results = p.map(square, [1, 2, 3, 4, 5])
    print(results)   # [1, 4, 9, 16, 25]
Advanced
49. What is Asyncio in Python?

Asyncio is Python's built-in library for writing concurrent code using the async/await syntax.

It uses a single thread with an event loop to handle many I/O operations concurrently, making it very efficient for network applications.

  • async def — defines a coroutine
  • await — suspends coroutine until result is ready
  • asyncio.gather() — runs multiple coroutines concurrently
  • Best for I/O-bound tasks with many concurrent connections
python
# Asyncio
import asyncio

async def fetch_data(name, delay):
    print(f"Fetching {name}...")
    await asyncio.sleep(delay)
    print(f"{name} done!")
    return f"Data from {name}"

async def main():
    results = await asyncio.gather(
        fetch_data("API-1", 2),
        fetch_data("API-2", 1),
        fetch_data("API-3", 3)
    )
    print(results)

asyncio.run(main())
Advanced
50. What is the Singleton Design Pattern?

The Singleton Pattern ensures that a class has only one instance throughout the application's lifecycle.

In Python, it is implemented by overriding __new__ to check if an instance already exists before creating a new one.

  • Only one instance ever created
  • Global access point to that instance
  • Common use: database connections, config managers, logging
  • Can also be implemented with metaclasses or modules
python
# 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
Advanced
51. What is the Factory Design Pattern?

The Factory Pattern provides an interface for creating objects without specifying the exact class to be instantiated.

It centralizes object creation logic and makes it easy to add new types without modifying client code.

  • Decouples object creation from usage
  • Promotes Open/Closed Principle
  • Makes code more extensible
  • Common in frameworks and plugin systems
python
# Factory Pattern
class Dog:
    def speak(self): return "Woof!"

class Cat:
    def speak(self): return "Meow!"

class AnimalFactory:
    @staticmethod
    def create(animal_type):
        animals = {"dog": Dog, "cat": Cat}
        cls = animals.get(animal_type.lower())
        if cls:
            return cls()
        raise ValueError(f"Unknown animal: {animal_type}")

animal = AnimalFactory.create("dog")
print(animal.speak())   # Woof!
Advanced
52. What is the Observer Design Pattern?

The Observer Pattern defines a one-to-many dependency where multiple observers are notified when the subject's state changes.

It is the foundation of event-driven programming and reactive systems.

  • Decouples publisher from subscribers
  • Used in event systems, GUI frameworks, and message queues
  • Supports multiple listeners per event
  • Basis of Python's signal libraries
python
# Observer Pattern
class EventEmitter:
    def __init__(self):
        self._listeners = {}

    def on(self, event, callback):
        self._listeners.setdefault(event, []).append(callback)

    def emit(self, event, *args):
        for cb in self._listeners.get(event, []):
            cb(*args)

emitter = EventEmitter()
emitter.on("data", lambda x: print(f"Received: {x}"))
emitter.on("data", lambda x: print(f"Logged: {x}"))
emitter.emit("data", "Hello!")
Intermediate
53. How does Binary Search work in Python?

Binary Search efficiently finds a target in a sorted array by repeatedly dividing the search space in half.

It achieves O(log n) time complexity, making it much faster than linear search for large datasets.

  • Requires sorted input
  • Time Complexity O(log n)
  • Space Complexity O(1) iterative
  • Python has bisect module for binary search
python
# Binary Search
def binary_search(arr, target):
    left, right = 0, len(arr) - 1

    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1

arr = [1, 3, 5, 7, 9, 11, 13]
print(binary_search(arr, 7))    # 3
print(binary_search(arr, 6))    # -1
Beginner
54. How does Bubble Sort work in Python?

Bubble Sort repeatedly swaps adjacent elements if they are in the wrong order, gradually moving larger elements to the end.

It is the simplest sorting algorithm but inefficient for large datasets.

  • Time Complexity O(n²)
  • Space Complexity O(1)
  • Stable sorting algorithm
  • Best for educational purposes and small datasets
python
# Bubble Sort
def bubble_sort(arr):
    n = len(arr)
    for i in range(n):
        for j in range(0, n-i-1):
            if arr[j] > arr[j+1]:
                arr[j], arr[j+1] = arr[j+1], arr[j]
    return arr

print(bubble_sort([64, 34, 25, 12, 22, 11, 90]))
Intermediate
55. How does Merge Sort work in Python?

Merge Sort is a divide and conquer algorithm that splits the array in half, recursively sorts each half, and merges them.

It guarantees O(n log n) performance in all cases, making it reliable for large datasets.

  • Time Complexity O(n log n) always
  • Space Complexity O(n)
  • Stable sorting algorithm
  • Best for linked lists and external sorting
python
# Merge Sort
def merge_sort(arr):
    if len(arr) <= 1:
        return arr

    mid = len(arr) // 2
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])

    return merge(left, right)

def merge(left, right):
    result = []
    i = j = 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i]); i += 1
        else:
            result.append(right[j]); j += 1
    return result + left[i:] + right[j:]

print(merge_sort([38, 27, 43, 3, 9, 82, 10]))
Intermediate
56. How does Quick Sort work in Python?

Quick Sort selects a pivot element and partitions the array around it, then recursively sorts the partitions.

It is one of the fastest sorting algorithms in practice due to excellent cache performance.

  • Average Time Complexity O(n log n)
  • Worst Case O(n²) with bad pivot selection
  • Space Complexity O(log n)
  • Not stable but very fast in practice
python
# Quick Sort
def quick_sort(arr):
    if len(arr) <= 1:
        return arr

    pivot = arr[len(arr) // 2]
    left = [x for x in arr if x < pivot]
    mid = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]

    return quick_sort(left) + mid + quick_sort(right)

print(quick_sort([3, 6, 8, 10, 1, 2, 1]))
Intermediate
57. How to implement a Stack in Python?

A Stack is a LIFO (Last In First Out) data structure that supports push, pop, and peek operations.

In Python, a stack can be implemented using a list or collections.deque for O(1) operations.

  • push() — add to top O(1)
  • pop() — remove from top O(1)
  • peek() — view top without removing O(1)
  • Used in: undo/redo, expression evaluation, DFS
python
# Stack Implementation
class Stack:
    def __init__(self):
        self.items = []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        if not self.is_empty():
            return self.items.pop()

    def peek(self):
        return self.items[-1] if self.items else None

    def is_empty(self):
        return len(self.items) == 0

    def size(self):
        return len(self.items)

s = Stack()
s.push(1); s.push(2); s.push(3)
print(s.pop())    # 3
print(s.peek())   # 2
Intermediate
58. How to implement a Queue in Python?

A Queue is a FIFO (First In First Out) data structure. Python's collections.deque provides O(1) enqueue and dequeue operations.

Python also provides queue.Queue for thread-safe queue operations in multi-threaded programs.

  • enqueue() — add to rear O(1)
  • dequeue() — remove from front O(1)
  • Use deque for best performance
  • Used in: BFS, task scheduling, print queues
python
# Queue Implementation
from collections import deque

class Queue:
    def __init__(self):
        self.items = deque()

    def enqueue(self, item):
        self.items.append(item)

    def dequeue(self):
        if not self.is_empty():
            return self.items.popleft()

    def is_empty(self):
        return len(self.items) == 0

    def size(self):
        return len(self.items)

q = Queue()
q.enqueue("A"); q.enqueue("B"); q.enqueue("C")
print(q.dequeue())   # A
print(q.size())      # 2
Intermediate
59. How to implement a Linked List in Python?

A Linked List is a linear data structure where each node contains data and a pointer to the next node.

Unlike arrays, linked lists provide O(1) insertion and deletion at known positions but O(n) for access by index.

  • Dynamic size — grows as needed
  • Efficient insert/delete at head or with pointer
  • No contiguous memory required
  • Foundation for stacks, queues, and hash tables
python
# Linked List
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class LinkedList:
    def __init__(self):
        self.head = None

    def append(self, data):
        new_node = Node(data)
        if not self.head:
            self.head = new_node
            return
        curr = self.head
        while curr.next:
            curr = curr.next
        curr.next = new_node

    def display(self):
        curr = self.head
        while curr:
            print(curr.data, end=" -> ")
            curr = curr.next
        print("None")

ll = LinkedList()
ll.append(1); ll.append(2); ll.append(3)
ll.display()
Advanced
60. How to implement a Binary Search Tree in Python?

A Binary Search Tree (BST) is a binary tree where left children are smaller and right children are larger than the parent.

It enables efficient O(log n) average-case search, insert, and delete operations.

  • Left subtree has smaller values
  • Right subtree has larger values
  • Inorder traversal gives sorted output
  • Can degrade to O(n) if unbalanced
python
# Binary Tree
class TreeNode:
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None

class BST:
    def __init__(self):
        self.root = None

    def insert(self, val):
        self.root = self._insert(self.root, val)

    def _insert(self, node, val):
        if not node:
            return TreeNode(val)
        if val < node.val:
            node.left = self._insert(node.left, val)
        else:
            node.right = self._insert(node.right, val)
        return node

    def inorder(self, node):
        if node:
            self.inorder(node.left)
            print(node.val, end=" ")
            self.inorder(node.right)

bst = BST()
for v in [5, 3, 7, 1, 4]:
    bst.insert(v)
bst.inorder(bst.root)   # 1 3 4 5 7
Advanced
61. How to implement BFS (Breadth First Search) in Python?

BFS explores all neighbors of a node before moving deeper, visiting nodes level by level using a queue.

It is used to find the shortest path in unweighted graphs and level-order tree traversal.

  • Uses a queue (FIFO)
  • Time Complexity O(V+E)
  • Space Complexity O(V)
  • Finds shortest path in unweighted graphs
python
# Graph - BFS
from collections import deque

def bfs(graph, start):
    visited = set()
    queue = deque([start])
    visited.add(start)

    while queue:
        node = queue.popleft()
        print(node, end=" ")
        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)

graph = {"A": ["B","C"], "B": ["D"], "C": ["E"], "D": [], "E": []}
bfs(graph, "A")   # A B C D E
Advanced
62. How to implement DFS (Depth First Search) in Python?

DFS explores as far as possible along each branch before backtracking, using a stack (or recursion).

It is used for cycle detection, topological sort, and solving maze/pathfinding problems.

  • Uses a stack or recursion
  • Time Complexity O(V+E)
  • Space Complexity O(V)
  • Used in topological sort, cycle detection
python
# Graph - DFS
def dfs(graph, node, visited=None):
    if visited is None:
        visited = set()
    visited.add(node)
    print(node, end=" ")

    for neighbor in graph[node]:
        if neighbor not in visited:
            dfs(graph, neighbor, visited)

graph = {"A": ["B","C"], "B": ["D"], "C": ["E"], "D": [], "E": []}
dfs(graph, "A")   # A B D C E
Intermediate
63. How to solve the Two Sum Problem in Python?

The Two Sum Problem finds two numbers in an array that add up to a given target and returns their indices.

Using a HashMap, we store complements as we iterate, achieving O(n) time and O(n) space.

  • Brute force: O(n²)
  • HashMap approach: O(n)
  • Store complement as key, index as value
  • Most common first coding interview question
python
# Two Sum Problem
def two_sum(nums, target):
    seen = {}
    for i, num in enumerate(nums):
        complement = target - num
        if complement in seen:
            return [seen[complement], i]
        seen[num] = i
    return []

print(two_sum([2, 7, 11, 15], 9))   # [0, 1]
print(two_sum([3, 2, 4], 6))        # [1, 2]
Advanced
64. What is Memoization using lru_cache in Python?

lru_cache (Least Recently Used Cache) is a decorator from functools that automatically memoizes function results.

It caches the most recent calls and evicts the least recently used when the cache is full.

  • @lru_cache(maxsize=None) — unlimited cache
  • Function must have hashable arguments
  • Dramatically speeds up recursive algorithms
  • Available in Python 3.2+; use @cache in Python 3.9+
python
# Fibonacci with memoization
from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n <= 1:
        return n
    return fib(n-1) + fib(n-2)

print([fib(i) for i in range(10)])
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Advanced
65. How to solve the Coin Change Problem in Python?

The Coin Change Problem finds the minimum number of coins needed to make a given amount using available coin denominations.

It is solved using bottom-up dynamic programming where dp[i] represents the min coins needed for amount i.

  • Time Complexity O(n * amount)
  • Space Complexity O(amount)
  • Returns -1 if amount cannot be made
  • Classic DP interview problem
python
# Dynamic Programming - Coin Change
def coin_change(coins, amount):
    dp = [float("inf")] * (amount + 1)
    dp[0] = 0

    for i in range(1, amount + 1):
        for coin in coins:
            if coin <= i:
                dp[i] = min(dp[i], dp[i - coin] + 1)

    return dp[amount] if dp[amount] != float("inf") else -1

print(coin_change([1, 5, 6, 9], 11))   # 2
Advanced
66. How to find the Longest Common Subsequence in Python?

The Longest Common Subsequence (LCS) finds the longest subsequence present in both strings while maintaining relative order.

It is solved using a 2D DP table where dp[i][j] stores the LCS length of first i and j characters.

  • Time Complexity O(m*n)
  • Space Complexity O(m*n)
  • Used in DNA analysis and diff tools
  • Subsequence need not be contiguous
python
# Longest Common Subsequence
def lcs(s1, s2):
    m, n = len(s1), len(s2)
    dp = [[0]*(n+1) for _ in range(m+1)]

    for i in range(1, m+1):
        for j in range(1, n+1):
            if s1[i-1] == s2[j-1]:
                dp[i][j] = dp[i-1][j-1] + 1
            else:
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])

    return dp[m][n]

print(lcs("ABCBDAB", "BDCAB"))   # 4
Advanced
67. What is Kadane's Algorithm in Python?

Kadane's Algorithm finds the maximum sum contiguous subarray in O(n) time using a dynamic programming approach.

At each step, it decides whether to extend the current subarray or start a new one from the current element.

  • Time Complexity O(n)
  • Space Complexity O(1)
  • Handles all-negative arrays (returns max single element)
  • Classic DP and greedy problem
python
# Kadane's Algorithm - Max Subarray
def max_subarray(nums):
    max_sum = current = nums[0]

    for num in nums[1:]:
        current = max(num, current + num)
        max_sum = max(max_sum, current)

    return max_sum

print(max_subarray([-2,1,-3,4,-1,2,1,-5,4]))   # 6
Intermediate
68. How to validate Parentheses in Python?

Valid parentheses checking ensures every opening bracket has a matching closing bracket in the correct order.

A stack is pushed with opening brackets and popped when a matching closing bracket is encountered.

  • Use a stack data structure
  • Use a mapping dict for bracket pairs
  • Time Complexity O(n)
  • Space Complexity O(n)
python
# Valid Parentheses
def is_valid(s):
    stack = []
    mapping = {")": "(", "}": "{", "]": "["}

    for char in s:
        if char in mapping:
            top = stack.pop() if stack else "#"
            if mapping[char] != top:
                return False
        else:
            stack.append(char)

    return not stack

print(is_valid("()[]{}"))    # True
print(is_valid("([)]"))      # False
Intermediate
69. How to Reverse a Linked List in Python?

Reversing a linked list changes the direction of all next pointers so the last node becomes the new head.

This is done iteratively using three pointers: prev, curr, and nxt.

  • Time Complexity O(n)
  • Space Complexity O(1) iterative
  • Can also be done recursively with O(n) space
  • Classic linked list interview question
python
# Reverse Linked List
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

def reverse_list(head):
    prev = None
    curr = head
    while curr:
        nxt = curr.next
        curr.next = prev
        prev = curr
        curr = nxt
    return prev

# Build list: 1 -> 2 -> 3
head = Node(1)
head.next = Node(2)
head.next.next = Node(3)

# Reverse it
new_head = reverse_list(head)
while new_head:
    print(new_head.data, end=" ")
    new_head = new_head.next
Advanced
70. How to Detect a Cycle in a Linked List in Python?

Floyd's Tortoise and Hare Algorithm uses two pointers moving at different speeds to detect a cycle in a linked list.

If a cycle exists, the fast pointer will eventually meet the slow pointer inside the cycle.

  • Slow pointer moves 1 step at a time
  • Fast pointer moves 2 steps at a time
  • Time Complexity O(n)
  • Space Complexity O(1) — no extra memory
python
# Detect Cycle in Linked List (Floyd's)
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

def has_cycle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow is fast:
            return True
    return False

# Create cycle: 1->2->3->4->2
n1, n2, n3, n4 = Node(1), Node(2), Node(3), Node(4)
n1.next = n2; n2.next = n3; n3.next = n4; n4.next = n2

print(has_cycle(n1))   # True
Advanced
71. How to solve the N-Queens Problem in Python?

The N-Queens Problem places N queens on an N×N chessboard so that no two queens attack each other.

It is solved using backtracking, placing one queen per row and checking column and diagonal conflicts.

  • Backtracking approach
  • Track columns and two diagonal sets
  • Time Complexity O(n!)
  • Classic constraint satisfaction problem
python
# N-Queens Problem
def solve_n_queens(n):
    result = []
    def backtrack(row, cols, diag1, diag2, board):
        if row == n:
            result.append(["".join(r) for r in board])
            return
        for col in range(n):
            if col in cols or (row-col) in diag1 or (row+col) in diag2:
                continue
            board[row][col] = "Q"
            cols.add(col); diag1.add(row-col); diag2.add(row+col)
            backtrack(row+1, cols, diag1, diag2, board)
            board[row][col] = "."
            cols.discard(col); diag1.discard(row-col); diag2.discard(row+col)
    board = [["."]*n for _ in range(n)]
    backtrack(0, set(), set(), set(), board)
    return len(result)

print(solve_n_queens(4))   # 2
Advanced
72. How to implement Dijkstra's Algorithm in Python?

Dijkstra's Algorithm finds the shortest path from a source node to all other nodes in a weighted graph with non-negative edge weights.

Python's heapq module provides an efficient min-heap priority queue for the implementation.

  • Uses a min-heap priority queue
  • Time Complexity O((V+E) log V)
  • Does not work with negative weights
  • Used in GPS navigation and routing
python
# Dijkstra's Algorithm
import heapq

def dijkstra(graph, start):
    dist = {node: float("inf") for node in graph}
    dist[start] = 0
    heap = [(0, start)]

    while heap:
        d, u = heapq.heappop(heap)
        if d > dist[u]:
            continue
        for v, w in graph[u]:
            if dist[u] + w < dist[v]:
                dist[v] = dist[u] + w
                heapq.heappush(heap, (dist[v], v))

    return dist

graph = {"A":[("B",1),("C",4)], "B":[("C",2),("D",5)], "C":[("D",1)], "D":[]}
print(dijkstra(graph, "A"))
Advanced
73. How to implement a Trie in Python?

A Trie is a tree-like data structure used to store strings where each node represents a single character of a word.

It enables O(m) search, insert, and prefix lookup where m is the length of the word.

  • Each node stores children as a dictionary
  • is_end flag marks end of a word
  • Supports prefix search efficiently
  • Used in autocomplete and spell checking
python
# Trie Data Structure
class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end = False

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word):
        node = self.root
        for ch in word:
            if ch not in node.children:
                node.children[ch] = TrieNode()
            node = node.children[ch]
        node.is_end = True

    def search(self, word):
        node = self.root
        for ch in word:
            if ch not in node.children:
                return False
            node = node.children[ch]
        return node.is_end

trie = Trie()
trie.insert("hello")
print(trie.search("hello"))    # True
print(trie.search("hell"))     # False
Advanced
74. How to implement an LRU Cache in Python?

An LRU (Least Recently Used) Cache evicts the least recently accessed item when the cache reaches its capacity.

Python's OrderedDict makes it easy to implement with O(1) get and put operations.

  • Get and Put both O(1)
  • move_to_end() marks item as recently used
  • popitem(last=False) evicts least recently used
  • Also available via @functools.lru_cache
python
# LRU Cache
from collections import OrderedDict

class LRUCache:
    def __init__(self, capacity):
        self.cap = capacity
        self.cache = OrderedDict()

    def get(self, key):
        if key not in self.cache:
            return -1
        self.cache.move_to_end(key)
        return self.cache[key]

    def put(self, key, value):
        if key in self.cache:
            self.cache.move_to_end(key)
        self.cache[key] = value
        if len(self.cache) > self.cap:
            self.cache.popitem(last=False)

lru = LRUCache(2)
lru.put(1, 1); lru.put(2, 2)
print(lru.get(1))   # 1
lru.put(3, 3)
print(lru.get(2))   # -1 (evicted)
Advanced
75. How to use Heaps and Priority Queues in Python?

Python's heapq module implements a min-heap, where the smallest element is always at the root.

For a max-heap, negate the values when pushing and negate again when popping.

  • heappush(heap, val) — insert in O(log n)
  • heappop(heap) — remove min in O(log n)
  • heapify(list) — convert list to heap in O(n)
  • nlargest(k) / nsmallest(k) for top-k problems
python
# Heap / Priority Queue
import heapq

# Min-Heap
heap = []
heapq.heappush(heap, 5)
heapq.heappush(heap, 1)
heapq.heappush(heap, 3)
print(heapq.heappop(heap))   # 1

# Max-Heap (negate values)
max_heap = []
for val in [5, 1, 3, 9]:
    heapq.heappush(max_heap, -val)
print(-heapq.heappop(max_heap))   # 9

# nlargest and nsmallest
nums = [3, 1, 4, 1, 5, 9, 2, 6]
print(heapq.nlargest(3, nums))    # [9, 6, 5]
print(heapq.nsmallest(3, nums))   # [1, 1, 2]
Advanced
76. How to solve Sliding Window Maximum in Python?

The Sliding Window Maximum problem finds the maximum element in every window of size k as it slides through the array.

A monotonic deque (decreasing) stores indices so the front always holds the max of the current window.

  • Uses a monotonic deque
  • Time Complexity O(n)
  • Space Complexity O(k)
  • Classic deque application in interviews
python
# Sliding Window Maximum
from collections import deque

def max_sliding_window(nums, k):
    dq = deque()
    result = []

    for i, num in enumerate(nums):
        while dq and dq[0] < i - k + 1:
            dq.popleft()
        while dq and nums[dq[-1]] < num:
            dq.pop()
        dq.append(i)
        if i >= k - 1:
            result.append(nums[dq[0]])

    return result

print(max_sliding_window([1,3,-1,-3,5,3,6,7], 3))
# [3, 3, 5, 5, 6, 7]
Intermediate
77. How to generate all Subsets (Power Set) in Python?

The Power Set is the collection of all possible subsets of a set, including the empty set and the full set.

For a set of n elements, there are 2ⁿ subsets. It is generated iteratively by doubling subsets at each step.

  • Iterative approach: O(n * 2ⁿ)
  • Can also use backtracking
  • Can also be done using bit manipulation
  • Classic combinatorics interview problem
python
# Subset Generation
def subsets(nums):
    result = [[]]
    for num in nums:
        result += [curr + [num] for curr in result]
    return result

print(subsets([1, 2, 3]))
# [[], [1], [2], [1,2], [3], [1,3], [2,3], [1,2,3]]
Intermediate
78. How to generate all Permutations in Python?

A Permutation is an arrangement of all elements in every possible order. For n elements, there are n! permutations.

Python's itertools.permutations() can generate them directly, or use backtracking for a custom solution.

  • Recursive backtracking approach
  • Time Complexity O(n * n!)
  • Also available via itertools.permutations()
  • Classic backtracking interview problem
python
# Permutations
def permute(nums):
    if len(nums) <= 1:
        return [nums]
    result = []
    for i, num in enumerate(nums):
        rest = nums[:i] + nums[i+1:]
        for perm in permute(rest):
            result.append([num] + perm)
    return result

print(permute([1, 2, 3]))
Advanced
79. How to solve Trapping Rain Water in Python?

The Trapping Rain Water problem calculates how much water can be trapped between elevation bars after rainfall.

The two-pointer approach uses left and right max heights tracked simultaneously for an O(n) O(1) solution.

  • Two pointer technique
  • Time Complexity O(n)
  • Space Complexity O(1)
  • Also solvable with prefix/suffix max arrays
python
# Trapping Rain Water
def trap(height):
    left, right = 0, len(height) - 1
    left_max = right_max = water = 0

    while left < right:
        if height[left] < height[right]:
            if height[left] >= left_max:
                left_max = height[left]
            else:
                water += left_max - height[left]
            left += 1
        else:
            if height[right] >= right_max:
                right_max = height[right]
            else:
                water += right_max - height[right]
            right -= 1

    return water

print(trap([0,1,0,2,1,0,1,3,2,1,2,1]))   # 6
Advanced
80. How to solve Product of Array Except Self in Python?

This problem returns an array where each element is the product of all other elements except itself, without using division.

It uses two passes — one for left running products and one for right running products — achieving O(n) time and O(1) extra space.

  • No division allowed
  • Time Complexity O(n)
  • Space Complexity O(1) extra
  • Two-pass prefix and suffix product technique
python
# Product of Array Except Self
def product_except_self(nums):
    n = len(nums)
    output = [1] * n

    left = 1
    for i in range(n):
        output[i] = left
        left *= nums[i]

    right = 1
    for i in range(n-1, -1, -1):
        output[i] *= right
        right *= nums[i]

    return output

print(product_except_self([1, 2, 3, 4]))   # [24,12,8,6]
Advanced
81. How to find the Longest Increasing Subsequence in Python?

The Longest Increasing Subsequence (LIS) finds the length of the longest subsequence where elements are in strictly increasing order.

The DP approach has O(n²) complexity, while the binary search + patience sorting approach achieves O(n log n).

  • DP approach: O(n²) time, O(n) space
  • Binary search approach: O(n log n)
  • Elements need not be contiguous
  • Classic DP interview problem
python
# Longest Increasing Subsequence
def lis(nums):
    dp = [1] * len(nums)

    for i in range(1, len(nums)):
        for j in range(i):
            if nums[j] < nums[i]:
                dp[i] = max(dp[i], dp[j] + 1)

    return max(dp)

print(lis([10, 9, 2, 5, 3, 7, 101, 18]))   # 4
Advanced
82. How to compute Edit Distance in Python?

Edit Distance (Levenshtein Distance) is the minimum number of operations (insert, delete, replace) needed to transform one string into another.

A 2D DP table is built where dp[i][j] is the edit distance between the first i characters of s1 and j characters of s2.

  • Time Complexity O(m*n)
  • Space Complexity O(m*n)
  • Used in spell checkers and DNA analysis
  • Three operations: insert, delete, replace
python
# Edit Distance
def edit_distance(s1, s2):
    m, n = len(s1), len(s2)
    dp = [[0]*(n+1) for _ in range(m+1)]

    for i in range(m+1): dp[i][0] = i
    for j in range(n+1): dp[0][j] = j

    for i in range(1, m+1):
        for j in range(1, n+1):
            if s1[i-1] == s2[j-1]:
                dp[i][j] = dp[i-1][j-1]
            else:
                dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])

    return dp[m][n]

print(edit_distance("sunday", "saturday"))   # 3
Advanced
83. How to solve the Word Break Problem in Python?

The Word Break Problem checks if a string can be segmented into a sequence of valid dictionary words.

dp[i] is True if the substring s[0:i] can be formed from words in the dictionary.

  • Bottom-up dynamic programming
  • Time Complexity O(n²)
  • Space Complexity O(n)
  • Used in NLP and text segmentation
python
# Word Break Problem
def word_break(s, word_dict):
    dp = [False] * (len(s) + 1)
    dp[0] = True
    word_set = set(word_dict)

    for i in range(1, len(s)+1):
        for j in range(i):
            if dp[j] and s[j:i] in word_set:
                dp[i] = True
                break

    return dp[len(s)]

print(word_break("leetcode", ["leet","code"]))   # True
Advanced
84. How to count the Number of Islands in Python?

The Number of Islands problem counts connected groups of '1's (land) in a 2D grid surrounded by '0's (water).

DFS is used to sink (mark as visited) all connected land cells when a new island is discovered.

  • DFS or BFS on 2D grid
  • Time Complexity O(m*n)
  • Space Complexity O(m*n) recursion stack
  • Classic graph traversal problem
python
# Number of Islands
def num_islands(grid):
    if not grid:
        return 0
    count = 0

    def dfs(r, c):
        if r<0 or r>=len(grid) or c<0 or c>=len(grid[0]) or grid[r][c]=="0":
            return
        grid[r][c] = "0"
        dfs(r+1,c); dfs(r-1,c); dfs(r,c+1); dfs(r,c-1)

    for r in range(len(grid)):
        for c in range(len(grid[0])):
            if grid[r][c] == "1":
                count += 1
                dfs(r, c)
    return count

grid=[["1","1","0"],["0","1","0"],["0","0","1"]]
print(num_islands(grid))   # 2
Advanced
85. How to traverse a Matrix in Spiral Order in Python?

Spiral Order Traversal visits matrix elements layer by layer in a clockwise direction from the outermost ring inward.

Four boundary pointers (top, bottom, left, right) shrink after each directional pass through the matrix.

  • Four boundary variable approach
  • Time Complexity O(m*n)
  • Space Complexity O(1) extra
  • Classic matrix interview problem
python
# Spiral Matrix
def spiral_order(matrix):
    result = []
    top, bottom = 0, len(matrix)-1
    left, right = 0, len(matrix[0])-1

    while top <= bottom and left <= right:
        for i in range(left, right+1): result.append(matrix[top][i])
        top += 1
        for i in range(top, bottom+1): result.append(matrix[i][right])
        right -= 1
        if top <= bottom:
            for i in range(right, left-1, -1): result.append(matrix[bottom][i])
            bottom -= 1
        if left <= right:
            for i in range(bottom, top-1, -1): result.append(matrix[i][left])
            left += 1

    return result

print(spiral_order([[1,2,3],[4,5,6],[7,8,9]]))
Advanced
86. How to implement Topological Sort in Python?

Topological Sort produces a linear ordering of vertices in a DAG (Directed Acyclic Graph) where every edge goes from earlier to later.

Kahn's Algorithm (BFS-based) uses in-degree counting and processes nodes with zero in-degree first.

  • Only works on Directed Acyclic Graphs
  • Time Complexity O(V+E)
  • Used in build systems and task scheduling
  • Can also be done using DFS with a stack
python
# Topological Sort
from collections import deque

def topo_sort(graph, n):
    in_degree = [0] * n
    for u in graph:
        for v in graph[u]:
            in_degree[v] += 1

    queue = deque([i for i in range(n) if in_degree[i] == 0])
    result = []

    while queue:
        node = queue.popleft()
        result.append(node)
        for neighbor in graph.get(node, []):
            in_degree[neighbor] -= 1
            if in_degree[neighbor] == 0:
                queue.append(neighbor)

    return result if len(result) == n else []

print(topo_sort({0:[1,2], 1:[3], 2:[3], 3:[]}, 4))
Advanced
87. How to implement Union-Find in Python?

Union-Find (Disjoint Set Union) tracks elements partitioned into non-overlapping sets with efficient union and find operations.

Path compression and union by rank optimize both operations to near O(1) amortized time.

  • Path compression flattens the tree during find
  • Union by rank keeps tree balanced
  • Near O(1) amortized per operation
  • Used in cycle detection and Kruskal's MST
python
# Union Find
class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [0] * n

    def find(self, x):
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]

    def union(self, x, y):
        px, py = self.find(x), self.find(y)
        if px == py: return False
        if self.rank[px] < self.rank[py]: px, py = py, px
        self.parent[py] = px
        if self.rank[px] == self.rank[py]: self.rank[px] += 1
        return True

uf = UnionFind(5)
uf.union(0, 1); uf.union(1, 2)
print(uf.find(0) == uf.find(2))   # True
Advanced
88. How to find the Minimum Cost Path in a Matrix using Python?

The Minimum Cost Path problem finds the path from the top-left to the bottom-right of a matrix with the minimum total cost.

It is solved using bottom-up dynamic programming where each cell stores the minimum cost to reach it.

  • Can move right, down, or diagonally
  • Time Complexity O(m*n)
  • Space Complexity O(m*n)
  • Classic 2D DP grid problem
python
# Min Cost Path (DP)
def min_cost_path(cost):
    m, n = len(cost), len(cost[0])
    dp = [[0]*n for _ in range(m)]
    dp[0][0] = cost[0][0]

    for i in range(1, m): dp[i][0] = dp[i-1][0] + cost[i][0]
    for j in range(1, n): dp[0][j] = dp[0][j-1] + cost[0][j]

    for i in range(1, m):
        for j in range(1, n):
            dp[i][j] = cost[i][j] + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])

    return dp[m-1][n-1]

cost = [[1,2,3],[4,8,2],[1,5,3]]
print(min_cost_path(cost))   # 8
Intermediate
89. How to implement String Compression in Python?

String Compression encodes consecutive repeated characters as the character followed by its count.

If the compressed string is not smaller, the original string is returned. It is a classic string manipulation problem.

  • Time Complexity O(n)
  • Space Complexity O(n)
  • Run-length encoding technique
  • Common in data compression algorithms
python
# String Compression
def compress(s):
    result = []
    i = 0
    while i < len(s):
        char = s[i]
        count = 0
        while i < len(s) and s[i] == char:
            i += 1
            count += 1
        result.append(char)
        if count > 1:
            result.append(str(count))
    return "".join(result)

print(compress("aabbbcccc"))   # a2b3c4
print(compress("abcd"))        # abcd
Intermediate
90. How to check Anagrams and Group Anagrams in Python?

Two strings are Anagrams if they contain the same characters with the same frequencies in any order.

Group Anagrams clusters strings from a list that are anagrams of each other using a sorted key as the dictionary key.

  • Use Counter or sorting to check anagrams
  • Time Complexity O(n * k log k) for grouping
  • Use sorted string as HashMap key
  • Very common string interview question
python
# Anagram Check
from collections import Counter

def is_anagram(s, t):
    return Counter(s) == Counter(t)

print(is_anagram("anagram", "nagaram"))   # True
print(is_anagram("rat", "car"))           # False

# Group Anagrams
def group_anagrams(strs):
    groups = {}
    for s in strs:
        key = tuple(sorted(s))
        groups.setdefault(key, []).append(s)
    return list(groups.values())

print(group_anagrams(["eat","tea","tan","ate","nat","bat"]))
Intermediate
91. How to find the Top K Frequent Elements in Python?

The Top K Frequent Elements problem returns the K most frequently occurring elements from a given list.

It can be solved efficiently using Counter.most_common(k) or a bucket sort approach for O(n) time.

  • Using Counter: O(n log n)
  • Using bucket sort: O(n)
  • Using heap: O(n log k)
  • Very common interview problem
python
# Top K Frequent Elements
from collections import Counter

def top_k_frequent(nums, k):
    count = Counter(nums)
    return [x for x, _ in count.most_common(k)]

print(top_k_frequent([1,1,1,2,2,3], 2))   # [1, 2]

# Using bucket sort approach O(n)
def top_k_bucket(nums, k):
    count = Counter(nums)
    buckets = [[] for _ in range(len(nums)+1)]
    for num, freq in count.items():
        buckets[freq].append(num)
    result = []
    for i in range(len(buckets)-1, 0, -1):
        result.extend(buckets[i])
        if len(result) >= k: break
    return result[:k]
Intermediate
92. How to generate Pascal's Triangle in Python?

Pascal's Triangle is a triangular array where each number is the sum of the two numbers directly above it in the previous row.

Each row starts and ends with 1, and it reveals many mathematical patterns like binomial coefficients.

  • Each row has one more element than the previous
  • Row n gives binomial coefficients C(n,0) to C(n,n)
  • Time Complexity O(n²)
  • Space Complexity O(n²)
python
# Pascal's Triangle
def generate_pascal(num_rows):
    triangle = []
    for i in range(num_rows):
        row = [1] * (i + 1)
        for j in range(1, i):
            row[j] = triangle[i-1][j-1] + triangle[i-1][j]
        triangle.append(row)
    return triangle

for row in generate_pascal(5):
    print(row)
Advanced
93. How to implement Fast Power (Exponentiation) in Python?

Fast Exponentiation (Exponentiation by Squaring) computes base^exp in O(log n) time instead of O(n).

It works by halving the exponent at each step and squaring the base, reducing the number of multiplications drastically.

  • Time Complexity O(log n)
  • Space Complexity O(log n) recursive
  • Handles negative exponents
  • Foundation of modular exponentiation in cryptography
python
# Power Function (Fast Exponentiation)
def my_pow(base, exp):
    if exp == 0: return 1
    if exp < 0:
        base = 1 / base
        exp = -exp
    if exp % 2 == 0:
        return my_pow(base * base, exp // 2)
    return base * my_pow(base * base, exp // 2)

print(my_pow(2, 10))     # 1024
print(my_pow(2.0, -2))   # 0.25
Advanced
94. How to perform Matrix Multiplication in Python?

Matrix Multiplication produces a new matrix where each element is the dot product of the corresponding row of the first matrix and column of the second.

For matrices A (m×k) and B (k×n), the result is a matrix of size (m×n). The number of columns in A must equal the number of rows in B.

  • Time Complexity O(m*k*n)
  • Space Complexity O(m*n)
  • Python also supports @ operator for matrix multiplication with NumPy
  • Foundation of neural network computations
python
# Matrix Multiplication
def matrix_multiply(A, B):
    rows_A, cols_A = len(A), len(A[0])
    rows_B, cols_B = len(B), len(B[0])

    if cols_A != rows_B:
        raise ValueError("Incompatible dimensions")

    result = [[0]*cols_B for _ in range(rows_A)]

    for i in range(rows_A):
        for j in range(cols_B):
            for k in range(cols_A):
                result[i][j] += A[i][k] * B[k][j]
    return result

A = [[1,2],[3,4]]
B = [[5,6],[7,8]]
for row in matrix_multiply(A, B):
    print(row)
Advanced
95. How to create a Decorator with Arguments in Python?

A Decorator with Arguments requires an extra outer function that accepts the arguments and returns the actual decorator.

This three-level function nesting pattern is the standard way to create parameterized decorators in Python.

  • Outer function accepts decorator arguments
  • Middle function is the actual decorator
  • Inner function is the wrapper
  • Use functools.wraps to preserve original function metadata
python
# Decorator with Arguments
def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")
# Hello, Alice!
# Hello, Alice!
# Hello, Alice!
Advanced
96. What is a Generator Pipeline in Python?

A Generator Pipeline chains multiple generators together where each generator lazily processes the output of the previous one.

This pattern is extremely memory-efficient for large data streams since values are only computed when needed.

  • Each stage processes one element at a time
  • O(1) memory regardless of data size
  • Lazy evaluation — nothing computed until consumed
  • Used in data pipelines, ETL processes, and stream processing
python
# Generator Pipeline
def integers():
    n = 1
    while True:
        yield n
        n += 1

def squares(gen):
    for n in gen:
        yield n * n

def take(n, gen):
    for _ in range(n):
        yield next(gen)

pipeline = take(5, squares(integers()))
print(list(pipeline))   # [1, 4, 9, 16, 25]
Advanced
97. What is a Metaclass in Python?

A Metaclass is a class whose instances are classes. It controls the creation, behavior, and structure of classes in Python.

The default metaclass in Python is type. Custom metaclasses allow you to intercept and modify class creation.

  • Classes are instances of their metaclass
  • type is the default metaclass
  • Override __call__ to control instantiation
  • Used in ORMs, frameworks, and Singleton patterns
python
# Metaclass
class SingletonMeta(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class Database(metaclass=SingletonMeta):
    def __init__(self):
        self.connection = "Connected"

db1 = Database()
db2 = Database()
print(db1 is db2)           # True
print(db1.connection)       # Connected
Advanced
98. What are Descriptors in Python?

A Descriptor is an object that defines how attribute access is handled by implementing __get__, __set__, or __delete__ methods.

Descriptors are the mechanism behind Python's property, staticmethod, and classmethod built-ins.

  • __get__ — called on attribute access
  • __set__ — called on attribute assignment
  • __delete__ — called on attribute deletion
  • Used to implement validation, type checking, and lazy loading
python
# Descriptors
class Validator:
    def __set_name__(self, owner, name):
        self.name = name

    def __get__(self, obj, type=None):
        if obj is None: return self
        return obj.__dict__.get(self.name)

    def __set__(self, obj, value):
        if not isinstance(value, int):
            raise TypeError(f"{self.name} must be an integer")
        if value < 0:
            raise ValueError(f"{self.name} must be non-negative")
        obj.__dict__[self.name] = value

class Product:
    price = Validator()
    quantity = Validator()

p = Product()
p.price = 100
p.quantity = 50
print(p.price, p.quantity)   # 100 50
Advanced
99. What is an Async Context Manager in Python?

An Async Context Manager is a context manager that uses async with syntax and supports awaitable __aenter__ and __aexit__ methods.

It is used for managing async resources like database connections, HTTP sessions, and file handles in async code.

  • __aenter__ — async setup on entering the block
  • __aexit__ — async cleanup on exiting the block
  • Used with async with statement
  • Common in aiohttp, asyncpg, and aiofiles libraries
python
# Async Context Manager
import asyncio

class AsyncDB:
    async def __aenter__(self):
        print("Connecting to DB...")
        await asyncio.sleep(0.1)
        return self

    async def __aexit__(self, *args):
        print("Closing DB connection...")
        await asyncio.sleep(0.1)

    async def query(self, sql):
        await asyncio.sleep(0.1)
        return f"Result of: {sql}"

async def main():
    async with AsyncDB() as db:
        result = await db.query("SELECT * FROM users")
        print(result)

asyncio.run(main())
Advanced
100. How to build a REST API using Flask in Python?

Flask is a lightweight Python web framework used to build REST APIs quickly with minimal boilerplate code.

It provides routing, request handling, and JSON response utilities out of the box, making it perfect for building backend services.

  • @app.route() — defines URL endpoints
  • request.json — parses incoming JSON body
  • jsonify() — converts dict to JSON response
  • Supports GET, POST, PUT, DELETE HTTP methods
python
# Full REST API with Flask
from flask import Flask, jsonify, request

app = Flask(__name__)

users = [
    {"id": 1, "name": "Alice", "email": "alice@example.com"},
    {"id": 2, "name": "Bob",   "email": "bob@example.com"}
]

@app.route("/users", methods=["GET"])
def get_users():
    return jsonify(users)

@app.route("/users/<int:user_id>", methods=["GET"])
def get_user(user_id):
    user = next((u for u in users if u["id"] == user_id), None)
    return jsonify(user) if user else ("Not Found", 404)

@app.route("/users", methods=["POST"])
def create_user():
    data = request.json
    data["id"] = len(users) + 1
    users.append(data)
    return jsonify(data), 201

if __name__ == "__main__":
    app.run(debug=True)