This interview guide is for educational and informational purposes only. It is designed to help readers prepare, but it does not guarantee any interview result, hiring decision, offer, or outcome. Interview questions, hiring criteria, and preferred answers can vary by employer, interviewer, industry, location, and time. The examples and explanations reflect the authors' research and judgment, are provided without warranties of any kind, and should not be treated as the only correct approach. Diagrams are simplified illustrations intended to highlight the main components and their interactions; actual systems and implementations may be more complex. Alternative approaches may be equally valid or better suited to a particular question, context, or interviewer. To the fullest extent permitted by applicable law, the author, contributors, and publisher are not liable for decisions made, actions taken, or losses incurred based on this guide.
Identity, Image, and Privacy Notice
To respect individual privacy, some names, profile photographs, avatars, biographical details, and other identifying information displayed in this guide may be replaced with pseudonyms, licensed stock images, illustrative avatars, composite images, or representative descriptions. Unless a person is expressly identified as an actual contributor, a displayed name, image, or profile should not be understood as depicting or identifying a specific candidate, interviewer, employee, or other real individual. These representations are provided for editorial and illustrative purposes only and do not imply endorsement, employment, participation, or affiliation with this guide or any company mentioned in it. Any resemblance to an actual person is coincidental.
Company Notice
This guide is an independent educational resource and is not affiliated with, endorsed by, sponsored by, or approved by the company named in this guide. Company names are used only to identify interview experiences commonly reported by candidates. Interview practices can change without notice, and inclusion of company-specific content does not mean these questions are official, complete, or guaranteed to be asked. To the fullest extent permitted by law, the author, contributors, and publisher are not responsible for outcomes related to use of this material.
Questions or comments?
Contact us for general questions, or share feedback, technical corrections, and comments with the community.
31. What are pip and PyPI?NEWLanguage SpecificEasy
i Question Details
Define pip as a Python package installer and PyPI as the default public index from which pip commonly discovers distributions. Explain python -m pip, project names and versions, wheels and source distributions, dependency resolution, requirements files, lock or environment reproducibility concerns, private indexes, and why an import package is not always named exactly like its distribution package.
Short Interview Answer (30-60 seconds)
pip is the common tool for installing Python distributions, while PyPI is the main public package index that pip searches by default. I usually run python -m pip so pip runs with the Python interpreter I intend to use. In real projects, I also control dependency versions because an installation can change over time if versions are not constrained or locked.
Detailed Explanation
pip is a tool that helps you add reusable software to a Python environment. PyPI is the main public place where many Python projects publish that software. A project can request one exact release or allow a range of acceptable releases. The installer may also need to install other software required by that project. Teams record dependency choices so development, testing, and production environments can stay consistent. Some companies use a private package source for internal software. The name used to install a project can also be different from the name used inside Python code.
Useful Questions to Ask the Interviewer
Should I also explain dependency reproducibility and private package sources?
Do you want an example of how a distribution name can differ from an import package name?
How to Explain It in an Interview
pip is Python's common package installer. PyPI is the default public package index that pip commonly searches for project metadata and distribution files. pip can also install from other configured indexes, local files, or supported source locations.
I prefer python -m pip because it runs pip with the selected Python interpreter. This reduces the chance of accidentally using pip from another Python environment.
When installing a project, pip resolves version requirements for that project and its dependencies. It selects versions that satisfy the available constraints. If a compatible wheel is available, pip normally uses it. A wheel is a built distribution that usually installs without building the project locally. Otherwise pip may use a source distribution, which can require a local build and build tools.
A requirements file records dependency requirements, but broad version ranges do not guarantee an identical environment later. Production teams often pin versions or use a lock based workflow with isolated environments. pip can also use private indexes for internal distributions. Finally, a distribution name used with pip does not have to match the package or module name used with import.
Where it is used
pip and PyPI are used when creating development environments, installing application dependencies, preparing test environments, building deployment images, and distributing reusable Python projects. Production teams often install from controlled dependency requirements or lock data. Organizations may also use private indexes for internal libraries or approved packages.
Why Interviewers Ask This
Interviewers ask this to check whether a Python developer understands how project dependencies are discovered, installed, versioned, and reproduced. They also want to see whether the candidate understands distribution names, import package names, public and private package sources, dependency resolution, and safe dependency practices for production environments.
Common interview mistakes
Common mistakes include treating pip and PyPI as the same thing, running a pip command that belongs to a different Python environment, assuming a requirements file automatically guarantees an identical environment, and ignoring transitive dependency versions. Another mistake is assuming the distribution name passed to pip must exactly match the package or module name used with import. Developers may also forget that pip can use private or alternative indexes instead of only PyPI.
Interview tip
Start by saying that pip installs Python distributions and PyPI is the public index that pip searches by default. Then briefly explain python -m pip, dependency resolution, wheels, source distributions, reproducibility, private indexes, and the difference between distribution names and import package names.
Interviewer may ask next
Why can the name passed to pip be different from the name used in an import statement?
They can differ because a distribution name identifies the installable project, while an import package or module name identifies Python code provided by that distribution. One distribution can provide one or more import packages with different names. This matters because developers should check the project's documentation instead of assuming the installation name is always the correct import name.
Is a requirements file enough to guarantee the same dependency environment in production?
Not always. A requirements file with broad version ranges can allow pip to select different valid versions at different times, including different transitive dependencies. For stronger reproducibility, teams can pin versions or use a lock based workflow and install inside an isolated environment. The tradeoff is that tighter version control improves repeatability but requires deliberate dependency updates for fixes and newer releases.
32. What are classes and objects in Python?NEWLanguage SpecificEasy
i Question Details
Define a class as a runtime object that creates a new type and an object as an instance of a type. Explain class bodies, instance attributes, class attributes, methods, object identity, encapsulating state and behavior, composition, and the dynamic nature of Python classes. Use one small example and explain when a function or simple data structure is clearer than a class.
Short Interview Answer (30-60 seconds)
A class in Python is a runtime object that represents a new type, and an object is an instance of a type. I use a class when related state and behavior belong together. Each instance can have its own attributes, while class attributes live on the class and can be found through instances. Methods provide behavior for instances. Python classes are dynamic objects, so code can inspect them, pass them around, and change attributes at runtime.
A class describes a kind of thing that a program needs to work with. An object is one real value made from that description. For example, one description can represent an account, while separate values can represent Asha's account and Ben's account. Each account can keep its own owner and balance, while both follow the same rules for actions such as adding money. This is useful when related information and actions belong together. For a very small task, a simple function, dictionary, list, or tuple may be easier to understand.
Useful Questions to Ask the Interviewer
Would you like me to explain the difference between values stored on each object and values stored on the class?
Should I also show a small Python example?
How to Explain It in an Interview
In Python, running a class statement executes the class body and creates a class object. That class object represents a new type. Calling it normally creates an instance of that type.
Names assigned in the class body become class attributes. Values assigned through self, such as self.balance, are instance attributes, so each instance can keep different state.
A method is a function stored on the class. When it is accessed through an instance, Python normally binds that instance to the method as self.
Every object also has an identity. Two objects can hold equal values and still be different objects. is checks identity, while == normally checks equality.
Classes group state and behavior and can use composition by storing other objects. Because classes are runtime objects, Python can inspect them, pass them to functions, and change attributes dynamically. Use a class when this structure makes a real concept clearer. Use a function or simple data structure when the problem does not need that extra structure.
Example
The example defines an Account class. The class attribute currency belongs to the class and can be found through each instance. The __init__ method stores owner and balance as instance attributes, so each Account object keeps its own values. The deposit method changes the balance of the specific instance passed as self. Two Account instances show that they use the same class but keep separate instance state. The final checks show that account_one has type Account and that the two variables refer to different objects.
Code
classAccount:
# This class attribute belongs to the Account class.
currency = "USD"def__init__(self, owner, balance=0):
# These instance attributes belong to each Account object.self.owner = owner
self.balance = balance
defdeposit(self, amount):
# This method changes only this Account object's balance.self.balance += amount
# Create two different instances of the same class.
account_one = Account("Asha", 100)
account_two = Account("Ben", 50)
# Change only the first instance.
account_one.deposit(25)
# Each instance keeps its own state.print(account_one.owner, account_one.balance)
print(account_two.owner, account_two.balance)
# Both instances can find the class attribute.print(account_one.currency)
print(account_two.currency)
# The first object is an Account instance.print(type(account_one) is Account)
# The two variables refer to different objects.print(account_one is account_two)
Where it is used
Classes are useful when production code has concepts that own both state and behavior. Common examples include user accounts, orders, service objects, configuration objects, and domain entities. Composition is useful when one object contains another object, such as an Order containing Customer information. A class is often unnecessary when code performs one small calculation or only groups a few values. In those cases, a function, dictionary, list, or tuple can be simpler to read and maintain.
Why Interviewers Ask This
Interviewers ask this to check whether the candidate understands what Python creates when a class statement runs and how instances use that class. They also want to see whether the candidate can explain instance attributes, class attributes, methods, object identity, composition, and the practical choice between a class and a simpler function or data structure.
Common interview mistakes
A common mistake is treating a class as something outside normal Python runtime behavior. In Python, the class itself is an object. Another mistake is confusing class attributes with instance attributes. A mutable class attribute, such as a list, can be shared by instances and cause unexpected changes. Candidates also sometimes confuse is with ==. The first checks object identity, while the second normally checks equality. Another mistake is creating a class for a tiny task where a function or simple data structure would be clearer.
Interview tip
Start with the main definition: a class is a runtime object representing a type, and an object is an instance of a type. Then use one small example to explain instance attributes, class attributes, and methods. Mention identity and composition briefly. Finish by explaining that a class is useful when related state and behavior belong together, but simpler tools are often better for small problems.
Interviewer may ask next
What happens if a class attribute contains a mutable object such as a list?
The mutable object can be shared by instances because attribute lookup can find the same object on the class. If one instance mutates that shared list, another instance can observe the change. This matters when each instance is expected to have independent state. If every object needs its own list, create the list as an instance attribute, usually inside __init__. The tradeoff is that each instance then stores its own list instead of sharing one object.
When would you use a function or simple data structure instead of creating a class?
I would use a function or simple data structure when the task has little state and no strong need to keep state and behavior together. For example, a function that converts one temperature value does not need a class. A dictionary or tuple may also be enough for a small group of values. This matters because classes add structure but also add more concepts and code. The main tradeoff is clarity. A class is useful for a meaningful object with related state and behavior, while simpler tools are usually easier to read for small problems.
33. What is self in a Python method?NEWLanguage SpecificEasy
i Question Details
Define self as the conventional name for the instance passed to an instance method. Explain method binding, why self appears explicitly in the function definition but is supplied through obj.method(), how it accesses instance state and other methods, and why self is a convention rather than a reserved keyword. Distinguish instance methods from class methods and static methods.
Short Interview Answer (30-60 seconds)
self is the conventional name for the current instance passed to an instance method. When I call obj.method(), Python binds that method to obj and supplies obj as the first argument, so I do not pass self myself. Inside the method, self lets me read or change that object's state and call its other methods. self is a convention, not a reserved Python word.
Detailed Explanation
In Python, an instance method usually works with one particular object. The first parameter in the method definition represents that object, and Python programmers normally call it self. For example, when you call obj.show(), Python supplies obj to the method as its first argument. This lets the method read or change information stored on that object and call other actions that belong to the same object. The name self is not a special reserved word. Another name can work, but self is the normal convention because it makes Python code easier to understand.
Useful Questions to Ask the Interviewer
Would you like me to explain how method binding supplies self?
Should I also compare instance methods with class methods and static methods?
How to Explain It in an Interview
The practical rule is simple. Use an instance method when the behavior needs a specific object's state or other instance methods.
When Python evaluates obj.show, it creates access to show that is bound to obj. Calling obj.show() therefore passes obj as the first argument to the underlying function. That is why the definition includes self even though the normal call does not.
Inside the method, self.value accesses state on that exact instance. self.save() calls another method using the same instance.
You can also call an instance method through the class, such as MyClass.show(obj). In that form, you supply the instance yourself. This shows that self is an ordinary parameter name, not a keyword.
A class method receives the class as its first argument, usually named cls. A static method receives no automatic instance or class argument. These choices do not add a special performance or memory benefit by themselves. Choose the method type based on what context the behavior actually needs.
Where it is used
Instance methods are common in production Python whenever an object keeps its own state. A service object can store configuration and use self to read it in several methods. A domain object can update its own fields through self. A model object can use instance methods that work with values belonging to one particular record. The important point is that self identifies the exact instance whose state and methods should be used.
Why Interviewers Ask This
Interviewers ask this to check whether the candidate understands how Python connects an object to an instance method. They want to see whether the candidate understands method binding, why the instance appears as an explicit parameter in the method definition, and why Python supplies that instance when the method is called through an object. They also test whether the candidate can distinguish instance methods from class methods and static methods.
Common interview mistakes
A common mistake is thinking self is a reserved Python keyword. It is only a naming convention. Another mistake is passing self manually in a normal call such as obj.method(self). Python already supplies the instance for a bound method call. Candidates may also forget the first instance parameter in the method definition. Another mistake is assuming every method receives self. A class method receives the class, usually as cls, while a static method receives no automatic instance or class argument.
Interview tip
Start by saying that self is the current instance passed to an instance method. Then explain that obj.method() binds the method to obj and Python supplies obj as the first argument. Mention that self is a convention rather than a reserved word. Finish by briefly comparing self with cls in a class method and with a static method that receives no automatic instance or class argument.
Interviewer may ask next
What happens if I call an instance method through the class instead of through an object?
You must supply the instance explicitly. For example, MyClass.show(obj) passes obj as the first argument to show. In contrast, obj.show() uses method binding, so Python supplies obj automatically. This matters because it shows that self is an ordinary function parameter and that the automatic behavior comes from binding the function through an instance.
When should I use an instance method instead of a class method or static method?
Use an instance method when the behavior needs state or methods from a specific object. It receives that instance as the first argument, conventionally named self. Use a class method when the behavior needs the class itself, conventionally received as cls. Use a static method when the operation needs neither instance state nor class state. The main tradeoff is clarity of required context. Choosing the narrowest suitable method type makes the code easier to understand and avoids unnecessary access to object or class state.
34. What is __init__ in Python?NEWLanguage SpecificEasy
i Question Details
Define __init__ as the instance-initialization method called after a new instance has been created. Explain its self parameter, constructor arguments, assigning valid initial state, inheritance and super().__init__, its required None return, and the difference between initialization in __init__ and object creation in __new__. Include a small class example.
Short Interview Answer (30-60 seconds)
__init__ initializes a new instance after Python has created it. It receives the instance through self and usually saves constructor arguments as instance attributes. With inheritance, a class often calls super().__init__ so required initialization earlier in the method resolution order can run. __init__ must return None. The actual creation of the instance is handled by __new__.
__init__ is the place where an object gets its starting information. For example, when we create an employee with a name, age, and role, this method can save those values inside the new employee. Python gives the method the new object automatically, so the method can change that object. This is useful because every new object can begin with valid values. A class can also reuse initialization from another class that it inherits from. This method does not make the object itself. It prepares an object that has already been created.
Useful Questions to Ask the Interviewer
Would you like me to explain the difference between __init__ and __new__?
Should I also show how __init__ works with inheritance?
How to Explain It in an Interview
When Python evaluates Employee("Maya", 30, "Developer"), object creation happens first. __new__ is responsible for creating and returning the new instance. Python then normally calls __init__ on that instance.
The self parameter refers to the instance being initialized. Statements such as self.name = name save constructor arguments as attributes on that instance. Simple validation can also happen here so the object begins in a valid state.
Employee calls super().__init__(name, age). In this example, that runs the User initializer, which stores the shared name and age values. Employee then stores role.
__init__ must return None. Returning another value causes a TypeError because __init__ initializes an existing instance rather than replacing it.
One runtime edge case is that if __new__ returns an object that is not an instance of the class, Python does not call that class's __init__.
In production code, keep __init__ focused on creating valid initial state. Expensive network calls or unrelated work can make instance creation slower and harder to test.
Example
The example defines a User class whose __init__ stores name and age on each instance. Employee inherits from User. Its __init__ calls super().__init__(name, age), which runs the User initializer in this inheritance structure. Employee then stores role. Creating Employee("Maya", 30, "Developer") produces an instance whose name is Maya, age is 30, and role is Developer. Neither __init__ method explicitly returns a value, so each returns None as required.
Code
classUser:
def__init__(self, name, age):
# Save the constructor arguments on this instance.self.name = name
self.age = age
classEmployee(User):
def__init__(self, name, age, role):
# Run the User initializer for the shared attributes.super().__init__(name, age)
# Save the attribute specific to Employee.self.role = role
# Python creates the Employee instance and then initializes it.
employee = Employee("Maya", 30, "Developer")
# Show the initialized state.print(employee.name)
print(employee.age)
print(employee.role)
Where it is used
__init__ is commonly used when creating application objects such as users, configuration objects, service classes, data models, and domain objects. It is useful for saving required values, checking simple input rules, and giving attributes sensible starting values. In inherited classes, super().__init__ is commonly used when initialization from another class in the method resolution order must also run.
Why Interviewers Ask This
Interviewers ask this to check whether a candidate understands how Python initializes instances, how self refers to the instance being initialized, how constructor arguments become instance state, how initialization works with inheritance, and why object creation with __new__ is different from initialization with __init__. It also checks whether the candidate knows that __init__ must return None.
Common interview mistakes
A common mistake is saying that __init__ creates the object. Object creation is handled by __new__, while __init__ initializes an instance after creation. Another mistake is returning a value other than None from __init__, which causes a TypeError. Developers may also forget self when assigning instance attributes, skip super().__init__ when required initialization from another class must run, or place expensive external work inside __init__, making instance creation slow and harder to test.
Interview tip
Start by saying that __init__ initializes an instance after it is created. Then explain self, constructor arguments, and assigning initial attributes. Mention that __init__ must return None. Finish by distinguishing it from __new__ and briefly show how super().__init__ supports inheritance.
Interviewer may ask next
What happens if __init__ returns a value other than None?
Python raises a TypeError if __init__ explicitly returns a value other than None. The exact behavior matters because __init__ is meant to initialize the instance that Python already created. It cannot replace that instance by returning another object. If custom control over object creation is needed, that behavior belongs in __new__ instead.
When should a child class call super().__init__?
A child class should call super().__init__ when required initialization earlier in the method resolution order needs to run. In the Employee example, the call runs the User initializer so name and age are set before Employee adds role. This matters because skipping required initialization can leave expected attributes unset. The main tradeoff is that the child depends on the initialization contract of the classes it cooperates with, but it avoids copying the same initialization logic.
35. What is an exception in Python?NEWLanguage SpecificEasy
i Question Details
Define an exception as an object that signals an abnormal condition and changes normal control flow until handled. Explain raising, try and except, matching exception types, else, finally, exception messages and tracebacks, custom exception classes, chaining, cleanup, and why code should catch the narrowest exception it can handle correctly.
Short Interview Answer (30-60 seconds)
An exception is an object that tells Python that an abnormal condition happened. When an exception is raised, normal control flow stops and Python looks for a matching except block. I catch the narrowest exception type that I can handle correctly, and I use finally when cleanup should run whether the operation succeeds or fails.
Detailed Explanation
An exception is Python's way of reporting that something abnormal happened while a program was running. For example, a program may try to open a missing file or turn invalid text into a number. Instead of continuing normally, Python changes the path of execution and looks for code that knows how to respond. This lets a program recover from expected problems, show a useful message, or stop safely. It also helps developers understand where a failure happened and make sure important cleanup work still occurs.
Useful Questions to Ask the Interviewer
Would you like a simple example using built in exceptions?
Should I also explain custom exceptions and exception chaining?
How to Explain It in an Interview
An exception in Python is an object. Python can raise one automatically when an operation fails, or code can raise one explicitly with raise.
Code that may fail goes inside try. An except block handles a compatible exception type. Python searches for a matching handler, so production code should catch the narrowest type it can handle correctly.
The else block runs when the try block completes without raising an exception. The finally block normally runs whether the operation succeeds or fails, so it is useful for cleanup.
An exception object can contain a message. If an exception remains unhandled, Python reports it with a traceback that shows the calls leading to the failure. Custom exceptions normally inherit from Exception directly or indirectly. Exception chaining, such as raise NewError from original_error, keeps the original cause visible. Broad exception handling can hide unexpected bugs, so it should be used only when the code has a clear recovery, logging, or shutdown responsibility.
Where it is used
Exceptions are used in production code when handling failures such as missing files, invalid input, failed conversions, network problems, database errors, and unavailable resources. They are also useful at application boundaries where code can log an error, return a useful response, retry an operation when appropriate, release resources, or allow the exception to continue to a higher level that can handle it correctly.
Why Interviewers Ask This
Interviewers ask this to check whether a candidate understands how Python represents abnormal conditions, changes normal control flow, matches exception handlers, keeps useful debugging information, and performs cleanup safely. They also want to see whether the candidate knows to catch only exception types that the program can handle correctly.
Common interview mistakes
Common mistakes include catching Exception when only one specific error is expected, using a bare except that hides problems that should remain visible, ignoring an exception without a clear reason, and putting too much unrelated code inside one try block. Another mistake is assuming that finally means the exception was handled. A finally block performs cleanup but does not automatically suppress the exception. Developers can also lose useful debugging context by raising a new exception without preserving the original cause when exception chaining would be clearer.
Interview tip
Start by saying that an exception is an object that signals an abnormal condition and changes normal control flow. Then explain raise, try, except, else, and finally in that order. Finish by saying that production code should catch the narrowest exception type it can handle correctly and preserve useful error context.
Interviewer may ask next
What happens if no except block matches the raised exception?
The exception keeps propagating through the active call stack until Python finds a compatible handler. If no handler is found, that execution context ends because of the unhandled exception, and Python normally reports the exception with a traceback. In a simple main program this usually terminates the program, while in a thread, task, or framework the surrounding runtime may handle the failure differently. This matters because an except block handles only compatible exception types and unexpected errors should remain visible.
Why should production code avoid catching Exception everywhere?
Production code should usually catch the narrowest exception type that it can handle correctly. Catching Exception broadly can also capture unexpected programming errors and make them harder to notice or debug. A broad handler can be appropriate at a clear application boundary for logging, cleanup, or controlled shutdown, but it should normally preserve or report the failure. The tradeoff is that broad catching gives one place to control failures, while increasing the risk of hiding defects that specific handlers would leave visible.
36. What is a Python decorator?NEWLanguage SpecificEasy
i Question Details
Define a decorator as a callable that receives a function, method, or class and returns a replacement or modified object, using @ syntax as convenient assignment. Explain one wrapper example, closures, *args and **kwargs, functools.wraps, decorator arguments, evaluation time, stacked decorators, and common uses such as logging, authorization, caching, registration, and retries.
Short Interview Answer (30-60 seconds)
A Python decorator is a callable that receives a function, method, or class and returns a replacement or modified object. The @ syntax is convenient syntax for assigning the decorated result back to the same name. A common decorator creates a wrapper that runs extra logic around the original function. In production code, I normally use functools.wraps so the wrapper keeps useful information such as the original function name and documentation.
A decorator lets us add the same extra behavior around existing work without rewriting that work. Imagine a greeting action that already works correctly. We may also want to record when it runs or check permission first. Instead of mixing those extra steps into the greeting itself, we can place them around it. This keeps the main job focused and makes the extra rule reusable. Python connects this extra behavior when it creates the decorated item, so the setup is normally done once when that definition is executed.
Useful Questions to Ask the Interviewer
Would you like a simple function decorator example?
Should I also explain decorators that accept their own arguments?
How to Explain It in an Interview
A decorator is a callable that receives a function, method, or class and returns a replacement or modified object. For a function, it often returns a wrapper.
The @ syntax is convenient assignment syntax. Writing @log_call above greet is effectively greet = log_call(greet). Decoration happens when Python executes that definition.
A wrapper often accepts *args and **kwargs to pass arguments through. It is a closure because it remembers the original function. functools.wraps preserves useful metadata.
A decorator can accept arguments through another outer callable. With stacked decorators, Python evaluates expressions from top to bottom, then applies decorators from the closest one outward.
Decorators suit logging, authorization, caching, registration, and retries. They add call overhead and another control flow layer, so direct code can be clearer for simple cases.
Example
The example uses a decorator factory named log_call. The outer function receives a prefix, so the decorator itself accepts an argument. The decorator function receives greet and returns wrapper. The wrapper remembers both prefix and greet through closures. It accepts *args and **kwargs and forwards them unchanged to greet. functools.wraps preserves useful metadata from greet. Python evaluates log_call("CALL") when it executes the decorated definition, then applies the returned decorator to greet. Calling greet later runs wrapper, prints the prefix, and then calls the original greet function.
Code
from functools import wraps
# This outer function lets the decorator accept its own argument.deflog_call(prefix):
# This function receives the function that will be decorated.defdecorator(func):
# wraps copies useful metadata from func to wrapper. @wraps(func)defwrapper(*args, **kwargs):
# This extra behavior runs before the original function.print(f"{prefix}: calling {func.__name__}")
# Forward all positional and keyword arguments unchanged.return func(*args, **kwargs)
# The decorator replaces the original name with this wrapper.return wrapper
# Return the actual decorator function.return decorator
# Python evaluates log_call("CALL") and applies its returned decorator# when this function definition is executed.@log_call("CALL")defgreet(name, punctuation="!"):
returnf"Hello, {name}{punctuation}"# Calling greet now calls the wrapper, which then calls the original function.print(greet("Maya", punctuation="!"))
Where it is used
Decorators are useful when many functions need the same surrounding behavior. Production examples include recording function calls, checking authorization before protected work, caching repeated results, registering handlers with a framework, measuring execution, and applying retry rules. Standard library tools such as functools.lru_cache also use decorator syntax. A wrapper normally adds another Python function call, and a closure keeps references to captured objects for as long as the wrapper is alive. Use decorators when the shared behavior is clear and reusable.
Why Interviewers Ask This
Interviewers ask this to check whether a candidate understands that Python functions and classes are objects that can be passed to other callables. They also want to see whether the candidate understands decoration time, closures, wrappers, metadata preservation with functools.wraps, decorator arguments, stacking order, and practical production uses.
Common interview mistakes
A common mistake is saying that a decorator only adds code before a function call. A decorator can return a replacement object and may change behavior in many ways. Another mistake is forgetting to return the original result from a wrapper. Developers also forget *args and **kwargs, which can make a wrapper reject arguments accepted by the original function. Forgetting functools.wraps can hide useful metadata. Another mistake is thinking decoration happens on every function call. Decorator expressions are evaluated when Python executes the decorated definition. With stacked decorators, candidates also sometimes confuse expression evaluation order with application order.
Interview tip
Start with the simple rule: a decorator receives an object and returns a replacement or modified object. Then explain @ syntax as assignment, show one wrapper, and mention closures, *args and **kwargs, and functools.wraps. Finish with one production use and explain that stacked decorator expressions are evaluated from top to bottom but applied from the closest decorator outward.
Interviewer may ask next
When is a Python decorator evaluated, and what happens with stacked decorators?
Decorator expressions are evaluated when Python executes the decorated definition. With stacked decorators, the expressions are evaluated from top to bottom, but the resulting decorators are applied from the closest decorator outward. For example, with outer above inner, the final result behaves like func = outer(inner(func)). This matters because each decorator receives the object produced by the decorator below it, so changing the order can change behavior.
What are the main tradeoffs of using decorators in production code?
Decorators are useful when the same surrounding behavior must be reused across many functions, such as logging, authorization, caching, registration, or retries. The tradeoff is that a wrapper normally adds another function call and another layer of control flow. A closure can also keep captured objects alive while the wrapper exists. This matters for readability, debugging, performance, and memory when decorators are used heavily. I use them when the shared behavior is clear and reusable, but I avoid unnecessary layers when direct code is easier to understand.
37. What are type hints in Python?NEWLanguage SpecificEasy
i Question Details
Define type hints as optional annotations that describe expected value types for readers, editors, linters, and static type checkers. Explain parameter, return, variable, collection, union, optional, protocol, and generic annotations, and make clear that normal Python execution does not automatically enforce most hints. Distinguish static checking from runtime validation.
Short Interview Answer (30-60 seconds)
Type hints are optional annotations that describe the types a function, variable, or collection is expected to use. They help readers, editors, linters, and static type checkers understand the code. Python normally does not enforce most type hints when the program runs, so a value with a different type can still reach the function. If runtime validation is required, the program needs separate validation logic or a library that performs it.
Detailed Explanation
Type hints are notes that describe what kind of value a part of a Python program is expected to receive, store, or return. They make code easier for people to understand and help development tools find some mistakes before the program runs. These notes can describe one value, a group of values, a value that may be missing, or several allowed kinds of values. The key point is that these notes usually guide people and tools. They do not normally stop a running Python program from receiving a different kind of value.
Useful Questions to Ask the Interviewer
Should I focus only on normal Python behavior, or also discuss runtime validation tools?
Would you like examples of modern annotation syntax?
How to Explain It in an Interview
Type hints are optional annotations. A parameter can be written as name: str, a return value as -> int, and a variable as count: int.
Collections can describe their contents, such as list[str] or dict[str, int]. A union such as str | int means either type is expected. str | None means a string or None is expected. A protocol describes required behavior rather than requiring one specific class. Generics let one annotation work with several related types while keeping useful type information.
Editors, linters, and static type checkers can inspect these annotations. Normal Python execution usually does not reject a value just because it conflicts with a hint. Python remains dynamically typed.
Use type hints for clearer interfaces, safer refactoring, better editor help, and static checking. Do not treat them as input validation. Values from users, files, networks, or external services should be validated at runtime when correctness or safety depends on the actual value.
Where it is used
Type hints are common in production Python libraries, service code, application interfaces, shared utility functions, and large code bases where developers need to understand expected values. They are useful for editor assistance, static checking, safer refactoring, clearer public interfaces, and describing collections or reusable components. Runtime validation is still needed when values come from untrusted or external sources and the program must confirm their real types or structure.
Why Interviewers Ask This
Interviewers ask this to check whether a candidate understands what Python type hints describe, how they help people and development tools, and what Python actually does with them during normal execution. They also want to see whether the candidate can separate static checking from runtime validation and choose useful annotations without assuming that hints automatically enforce types.
Common interview mistakes
A common mistake is saying that Python automatically rejects values that do not match type hints. Normal execution usually does not do that. Another mistake is confusing static type checking with runtime validation. Static checking examines annotations without relying on normal program execution, while runtime validation examines actual values while the program runs. Candidates also sometimes think str | None means a required string. It means either a string or None is expected. Another mistake is adding complicated annotations that make simple code harder to understand without providing useful checking value.
Interview tip
Start by saying that type hints describe expected types but normally do not enforce them at runtime. Then give one parameter and return example, mention collections and unions, and finish by clearly separating static checking from runtime validation.
Interviewer may ask next
What happens if I pass a value that does not match a Python type hint?
Normal Python execution usually still accepts the call because most type hints are not automatically enforced at runtime. For example, a function annotated with a str parameter can still receive another type unless the function or another runtime tool checks it. A static type checker may report the mismatch. This matters because annotations improve guidance and static checking, but they are not a replacement for runtime validation when actual input must be verified.
When should a production Python application use runtime validation in addition to type hints?
Use runtime validation when the real value must be checked while the program is running, especially for input from users, files, APIs, networks, or other external systems. Type hints still help developers and static checking tools understand the expected type or structure. Runtime validation adds actual enforcement. The tradeoff is extra code and runtime work, so controlled internal values may only need type hints, while untrusted boundaries often need both.
38. What is a Python dataclass?NEWLanguage SpecificEasy
i Question Details
Define a dataclass as a normal Python class decorated with @dataclass so common special methods can be generated from annotated fields. Explain generated __init__, repr, equality, defaults and default factories, frozen and slots options, post-initialization, inheritance, and the difference between a dataclass, a plain dictionary, and a validation or serialization library.
Short Interview Answer (30-60 seconds)
A Python dataclass is a normal class decorated with @dataclass. Python can generate common methods such as init, repr, and eq from the annotated fields. I use it when a class mainly stores related data and I still want clear named fields and normal class behavior. For mutable defaults such as lists, I use default_factory so each object gets its own value.
Detailed Explanation
A dataclass is useful when you want to group related information into one clear object without writing the same setup code again and again. You describe the pieces of information that the object should hold, and Python can create much of the routine setup for you. This makes the class shorter and easier to read. It works well for records such as a user, order, configuration, or message. It is still a normal class, so you can add your own methods and rules when needed.
Useful Questions to Ask the Interviewer
Would you like me to cover options such as frozen and slots?
Should I compare dataclasses with dictionaries and validation libraries?
How to Explain It in an Interview
A dataclass is a normal Python class decorated with @dataclass. Python reads its annotated fields and can generate methods such as init, repr, and eq. By default, equality compares the class and the values of fields marked for comparison.
Fields can have default values. For mutable values such as lists, use field with default_factory so each instance receives a new object. This avoids sharing one mutable value between instances.
The frozen option blocks normal assignment to dataclass fields after creation. It does not make contained mutable objects deeply immutable, and it is not a security boundary. The slots option asks the dataclass to create a class with slots. This can reduce per instance memory use because instances normally do not need their own dict, although inheritance and base class behavior can affect the final layout.
__post_init__ runs after the generated init finishes and is useful for derived values or extra checks. Dataclasses support inheritance, but required fields cannot follow fields with defaults across the final inherited field order.
Use a dataclass for structured application data with normal class behavior. A dictionary is more flexible but gives less declared structure. A dataclass also does not automatically perform runtime type validation or provide a complete external data validation and serialization system.
Where it is used
Dataclasses are useful for configuration objects, domain records, messages passed between parts of an application, parsed internal data, test fixtures, and small result objects. They work well when the fields are known in advance and the object may also need methods. In production, default_factory is important for mutable fields. frozen can express that normal field reassignment should not happen after creation. slots can reduce memory use when many small instances are created, but the actual benefit depends on the class hierarchy and workload.
Why Interviewers Ask This
Interviewers ask this question to check whether a candidate understands how Python can remove repetitive class code while keeping normal class behavior. They also want to see whether the candidate understands generated methods, field defaults, mutable defaults, equality, frozen objects, slots, inheritance, and initialization hooks. A strong answer also shows good judgment about when a dataclass is enough and when a dictionary or a separate validation or serialization library is a better choice.
Common interview mistakes
A common mistake is thinking a dataclass is a special container that is separate from normal Python classes. It is still a normal class. Another mistake is expecting field annotations to validate values at runtime automatically. They do not. Developers may also expect a dataclass to provide complete JSON serialization automatically, which it does not. Mutable defaults such as lists should use default_factory so each instance gets its own object. frozen does not make contained mutable objects deeply immutable. With inheritance, required and default field ordering must remain valid across the combined inherited fields.
Interview tip
Start by saying that a dataclass is a normal class where @dataclass can generate common methods from annotated fields. Then mention init, repr, eq, defaults, and default_factory. Finish with frozen, slots, post_init, inheritance, and the key distinction that a dataclass gives structure and convenience but does not automatically provide runtime validation or a complete serialization system.
Interviewer may ask next
What happens if a dataclass field needs a list as its default value?
Use field with default_factory so Python calls a factory for each new instance and gives that instance its own list. This matters because mutable state should not be unintentionally shared between instances. The main tradeoff is a small amount of extra setup syntax in exchange for correct and predictable object state.
When would you choose a dataclass instead of a dictionary or a validation library?
Choose a dataclass when the data has a known structure and you want named fields, generated class methods, and normal class behavior. Choose a dictionary when the shape is loose or highly dynamic. Choose a validation or serialization library when external data needs runtime validation, conversion, schemas, or richer serialization support. The tradeoff is that a dataclass is simple and part of the Python standard library, but it does not provide those larger validation and serialization features by itself.
39. What are async and await in Python?NEWLanguage SpecificEasy
i Question Details
Define async def as creating a coroutine function and await as suspending that coroutine until an awaitable can make progress. Explain the event loop, cooperative scheduling, tasks, asynchronous I/O, sequential versus concurrent awaits, cancellation, blocking calls, and why asyncio helps I/O-bound concurrency but does not automatically make CPU-bound Python code faster.
Short Interview Answer (30-60 seconds)
async def creates a coroutine function, and await lets its coroutine pause while an awaitable is not ready to complete. Control can then return to the event loop so other ready tasks can run. This is useful for input and output work such as network requests. Awaiting independent operations one after another is still sequential, while scheduling them as tasks can let their waiting periods overlap. asyncio does not automatically make CPU intensive Python code faster.
The practical idea is to avoid wasting time while a program waits. Imagine a program starts a network request and must wait for a reply. Instead of doing nothing during that wait, Python can pause that piece of work and let other ready work continue. When the requested operation becomes ready, Python can continue the paused work. This is useful when a program spends much of its time waiting for networks, databases, files, or other services. It does not mean that every type of Python work becomes faster.
Useful Questions to Ask the Interviewer
Would you like a small asyncio example?
Should I also explain sequential and concurrent waits?
How to Explain It in an Interview
In Python, async def creates a coroutine function. Calling it creates a coroutine object. Its body starts running only when the coroutine is awaited or scheduled.
Inside a coroutine, await works with an awaitable object. If that operation is not ready, the coroutine can suspend and give control back to the event loop. The event loop then runs other ready tasks. This is cooperative scheduling because running code must reach a point that allows other work to run.
A Task schedules a coroutine with the event loop. Awaiting two independent operations one after another keeps them sequential. Creating both tasks first can let their waiting periods overlap.
Cancellation is normal in asyncio. A cancelled task usually receives asyncio.CancelledError when cancellation is delivered, so cleanup should use constructs such as try and finally when needed.
Blocking calls and CPU intensive Python work can stop the event loop from serving other tasks. asyncio is mainly useful for input and output concurrency. It does not automatically speed up CPU intensive Python code.
Example
The example creates two coroutine tasks before waiting for their results. Each coroutine reaches asyncio.sleep, which suspends that coroutine without blocking the event loop. While one task is waiting, the event loop can run the other ready task. asyncio.gather waits for both scheduled tasks and returns their results. This demonstrates concurrent waiting for independent input and output style operations without claiming that Python executes the coroutine bodies in parallel.
Code
import asyncio
asyncdeffetch(name: str, delay: float) -> str:
# Define an asynchronous operation that represents waiting for input or output.print(f"Starting {name}")
# Pause this coroutine without blocking the event loop.await asyncio.sleep(delay)
print(f"Finished {name}")
return name
asyncdefmain() -> None:
# Schedule both coroutines before waiting for their results.# Their waiting periods can overlap because the event loop can run# another ready task while one task is suspended at await.
first_task = asyncio.create_task(fetch("first", 1.0))
second_task = asyncio.create_task(fetch("second", 1.0))
# Wait for both tasks and collect their results.
first_result, second_result = await asyncio.gather(
first_task,
second_task,
)
print(first_result, second_result)
# Create an event loop, run main until it finishes, and close the loop.
asyncio.run(main())
Where it is used
asyncio is useful in production services that handle many independent operations that spend time waiting. Examples include calling several web services, handling many network connections, waiting for database operations through an asynchronous driver, and coordinating background input and output work. It is most useful when many waits can overlap without blocking the event loop.
Why Interviewers Ask This
Interviewers ask this to check whether a candidate understands how Python handles waiting work without blocking other useful work. They want to see knowledge of coroutine functions, awaitable objects, the event loop, tasks, cooperative scheduling, cancellation, blocking calls, and the difference between input and output concurrency and CPU intensive work.
Common interview mistakes
A common mistake is thinking that async makes every function run in parallel. It does not. Another mistake is awaiting independent operations one by one and expecting concurrency. That keeps their waits sequential. Developers may also call blocking functions inside a coroutine. A blocking call can stop the event loop and delay other tasks. Another mistake is ignoring cancellation and cleanup. Finally, asyncio should not be treated as an automatic speed improvement for CPU intensive Python work.
Interview tip
Start with the practical idea that async and await let Python use waiting time for other work. Then explain that async def creates a coroutine function, await can suspend its coroutine, and the event loop runs other ready tasks. Clearly separate sequential awaits from concurrently scheduled tasks, and mention that blocking or CPU intensive work can still stop the event loop.
Interviewer may ask next
What happens if I call a blocking function inside an async function?
The blocking function can block the event loop thread. While it is running, other asyncio tasks on that loop may not get a chance to make progress. This matters because cooperative scheduling depends on running code returning control to the event loop. For blocking input and output work that has no asynchronous interface, asyncio.to_thread can move the call to a worker thread when appropriate. CPU intensive work may instead need a separate process so it does not block the event loop.
What is the difference between awaiting two coroutines sequentially and scheduling them as tasks?
Sequential awaits wait for the first operation to finish before continuing to the second await. If the operations are independent, scheduling both as tasks first lets the event loop make progress on either task while the other is waiting. This can reduce total waiting time for input and output operations. The tradeoff is added coordination, cancellation, error handling, and resource management, so concurrent tasks should be used when the operations are independent and can safely overlap.
40. Why is indentation significant in Python?Language SpecificEasy
i Question Details
Explain how indentation defines suites and block structure, what errors inconsistent indentation can cause, and why consistent indentation is essential for readable Python code.
Short Interview Answer (30-60 seconds)
Indentation is significant in Python because it defines code blocks. Statements at the same indentation level belong to the same suite, while deeper indentation creates a nested suite. Incorrect indentation can raise IndentationError or TabError. Valid indentation at the wrong level can also change program behavior without raising an error. In production code, I use four spaces consistently so the control flow is clear and predictable.
Detailed Explanation
Indentation is significant because Python uses it to define blocks of code. A block is also called a suite. It is the group of statements controlled by an if statement, loop, function, class, try statement, or with statement. Statements at the same indentation level belong to the same suite. Moving farther to the right starts a nested suite. Returning to an earlier level ends the current suite.
Useful Questions to Ask the Interviewer
Should I focus on Python language behavior, or also explain the runtime and standard library?
Which Python version and execution environment should I assume?
Would you like a small code example together with production tradeoffs and edge cases?
Python uses visible indentation instead of braces to show program structure. The parser reads indentation before the program runs and uses it to determine block boundaries. Indentation therefore adds no separate runtime operation and no meaningful memory cost.
Invalid indentation can raise IndentationError. Mixing tabs and spaces in a way that makes indentation levels unclear can raise TabError. A more dangerous problem is valid indentation at the wrong level. The program may run, but a statement may execute inside a condition or loop when it should execute outside it.
Blank lines and comment only lines do not create or end suites. Indentation used to align a continued expression inside parentheses does not create a new block. In production code, teams normally use four spaces, editor checks, formatters, code review, and tests to keep control flow clear.
Where it is used
Indentation is used throughout Python applications. It defines function bodies, class bodies, conditions, loops, exception handling, context managers, and nested control flow. In production services, scripts, web applications, data pipelines, and tests, consistent indentation helps developers understand which statements run together and prevents code from being placed in the wrong block.
Why Interviewers Ask This
Interviewers ask this question to check whether the candidate understands that indentation is part of Python syntax, not only a visual formatting choice. It also tests whether the candidate can identify block boundaries, diagnose indentation errors, and prevent logic from being placed in the wrong scope.
Common interview mistakes
Common mistakes include treating indentation as optional formatting, mixing tabs and spaces, using different indentation widths at the same logical level, and placing a statement inside the wrong condition or loop. Another mistake is assuming that code is correct because it runs. Valid indentation can still produce incorrect behavior when a statement belongs to the wrong suite. Developers may also wrongly assume that alignment inside parentheses creates a new block.
Interview tip
Start by saying that indentation defines Python code blocks. Then explain suites, nesting, IndentationError, TabError, and the risk of valid but misplaced indentation. Finish by mentioning four spaces and consistent editor settings.
Interviewer may ask next
Does indentation inside parentheses create a new Python block?
No. Indentation inside open parentheses, brackets, or braces is normally used to align a continued expression. It does not create a suite or change control flow. This matters because only indentation associated with a compound statement and its block defines a new scope of execution. Developers should still align continued lines consistently so the expression remains readable.
What happens when tabs and spaces are mixed in Python indentation?
Python can raise TabError when tabs and spaces are mixed in a way that makes indentation levels inconsistent. This matters because editors can display tab characters at different widths. Using tabs may reduce typed characters, but it makes visual alignment less reliable across tools. The safer production choice is to use four spaces and configure the editor to insert spaces.
More questions load as you scroll
Disclaimer: This interview guide is for educational and informational purposes only. It is designed to help readers prepare, but it does not guarantee any interview result, hiring decision, offer, or outcome. Interview questions, hiring criteria, and preferred answers can vary by employer, interviewer, industry, location, and time. The examples and explanations reflect the authors' research and judgment, are provided without warranties of any kind, and should not be treated as the only correct approach. Diagrams are simplified illustrations intended to highlight the main components and their interactions; actual systems and implementations may be more complex. Alternative approaches may be equally valid or better suited to a particular question, context, or interviewer. To the fullest extent permitted by applicable law, the author, contributors, and publisher are not liable for decisions made, actions taken, or losses incurred based on this guide.