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.
1. Why are Python strings immutable?Language SpecificEasy
i Question Details
Explain what string immutability means, how apparent modifications create new strings, and how immutability affects hashing, sharing, and repeated concatenation.
Short Interview Answer (30-60 seconds)
Python strings are immutable, so their character sequence cannot change after creation. Operations such as replace, upper, slicing, and concatenation return a string value instead of changing the original object. This makes strings safe to share and suitable as dictionary keys, but repeated concatenation can create extra copying and temporary string objects.
Detailed Explanation
Python strings are immutable. This means the sequence of characters inside a string object cannot change after Python creates it. Code cannot replace a character by assigning to an index.
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?
Operations that appear to modify a string return a string value instead. For example, replace, upper, slicing, and concatenation leave the original value unchanged. When the result is assigned to the same variable, the variable is rebound. It now refers to the result rather than changing the old object.
This behavior makes strings safe to share. Several variables can refer to the same string without one part of the program changing the value seen by another part. A string also keeps the same value and hash during its lifetime. This allows it to be used as a dictionary key or set member.
The main performance concern appears when many pieces are added repeatedly. Each concatenation may allocate another string and copy characters. Some Python implementations can optimize certain simple cases, but code should not depend on that optimization. For many pieces, store them in a list and call join once. Join usually reduces repeated copying and temporary allocations.
Where it is used
String immutability is useful for dictionary keys, set members, cache keys, file paths, configuration values, identifiers, log text, protocol messages, and values shared between functions. In production code, join is commonly used when a program must combine many small string pieces into one result.
Why Interviewers Ask This
Interviewers ask this question to check whether a candidate understands immutable objects, variable rebinding, hashing, object sharing, allocation, and the performance cost of building strings repeatedly. It also tests whether the candidate can choose an efficient string building method in production code.
Common interview mistakes
A common mistake is saying that assigning a new value to the same variable changes the original string. The assignment only rebinds the variable. Another mistake is trying to assign to a string index, which raises a TypeError. Developers may also build a large string with repeated concatenation inside a loop and overlook the possible copying and temporary allocations. Another mistake is using is to compare string values. Value comparison should use == because Python may reuse some string objects, but object identity is not guaranteed.
Interview tip
Begin by saying that the characters inside a string object cannot change. Then explain that apparent modifications return a string value and may rebind the variable. Finish with the practical effects: safe sharing, stable hashing, and possible copying during repeated concatenation.
Interviewer may ask next
What happens if code assigns a new character to a string index?
Python raises a TypeError because strings do not support item assignment. The existing character sequence cannot be changed. The program must create a new string, such as by combining slices with the replacement character. This matters because strings cannot be updated in place like lists.
Why is join usually better than repeated concatenation for many pieces?
Join usually performs less repeated copying because it combines all pieces into the final result in one operation. Repeated concatenation may create temporary strings and copy characters many times, although some implementations optimize simple cases. Concatenation is still clear for a small number of pieces, while join is the safer production choice for a large or growing collection.
2. What is inheritance in Python?NEWLanguage SpecificEasy
i Question Details
Define inheritance as creating a class that derives behavior and attributes from one or more base classes. Explain method overriding, super(), isinstance, issubclass, method resolution order, multiple inheritance, and the tradeoff between inheritance and composition. Use one small example and avoid presenting inheritance as the default reuse mechanism.
Short Interview Answer (30-60 seconds)
I use inheritance when one class is truly a more specific kind of another class. In Python, a derived class can use attributes and methods from one or more base classes, and it can override methods when its behavior needs to differ. super() can continue method lookup through the method resolution order. I do not treat inheritance as the default way to reuse code because composition often creates a simpler and more flexible relationship.
Use inheritance when one kind of object is clearly a more specific kind of another. It lets the new kind receive shared data and actions from the older kind, while still changing an action when needed. For example, a dog can use the general behavior of an animal and add its own sound. This can reduce repeated work and keep related behavior together. However, it should not be used only to share code. If one object simply needs help from another object, keeping them separate is often easier to change.
Useful Questions to Ask the Interviewer
Would you like me to explain multiple inheritance as well?
Should I compare inheritance with composition?
How to Explain It in an Interview
In Python, class Dog(Animal) makes Animal a base class of Dog. A Dog object can use attributes and methods found on Animal.
If Dog defines speak() again, it overrides the inherited method. super().speak() continues method lookup after Dog according to Python's method resolution order, called MRO. Here, it reaches Animal.speak().
isinstance(dog, Animal) checks the object relationship. issubclass(Dog, Animal) checks the class relationship.
Python also supports multiple inheritance. A class can have several base classes, and the MRO decides the lookup order.
Use inheritance for a real type relationship with shared behavior. Prefer composition when one object only needs another object's service. Composition often reduces coupling and makes parts easier to replace.
Inherited methods are not copied into every instance. They stay on classes and are found during attribute lookup. Each instance still stores its own instance state.
Example
The example defines Animal as the base class and Dog as the derived class. Dog.speak() overrides Animal.speak(). Inside the override, super().speak() continues lookup through the MRO and reaches Animal.speak() in this class structure. The example also shows that isinstance() recognizes the object as an Animal, that issubclass() recognizes the class relationship, and that Dog.mro() exposes the method lookup order.
Code
classAnimal:
# This method provides behavior that derived classes can reuse.defspeak(self):
return"An animal makes a sound"classDog(Animal):
# This method overrides the method inherited from Animal.defspeak(self):
# super() continues lookup through Python's method resolution order.
parent_message = super().speak()
returnf"{parent_message}. A dog barks"# Create one instance of the derived class.
dog = Dog()
# Call the overridden method.print(dog.speak())
# Check whether the object is an Animal or a derived type of Animal.print(isinstance(dog, Animal))
# Check the relationship between the two classes.print(issubclass(Dog, Animal))
# Show the order Python follows when looking for methods.print([cls.__name__ for cls in Dog.mro()])
Where it is used
Inheritance is useful in production when several classes have a real type relationship and share stable behavior. Frameworks may provide a base class with standard behavior that derived classes customize. It is also useful when code accepts a base type and should work with several derived types. Composition is usually a better choice when an object only needs to use another object's service rather than being a more specific form of that object.
Why Interviewers Ask This
Interviewers ask this to check whether a candidate understands how Python classes can reuse and change behavior from other classes. They also want to see whether the candidate understands overriding, super(), type checks, method lookup order, multiple inheritance, and when composition is a better design choice.
Common interview mistakes
A common mistake is using inheritance only because two classes share some code. Shared code alone does not create a good type relationship. Another mistake is assuming that super() always means the immediate parent class. It actually continues lookup according to the MRO. Developers can also misunderstand multiple inheritance and ignore how the full MRO affects method lookup. Another mistake is forgetting that isinstance() can return true when an object belongs to a class derived from the class being checked.
Interview tip
Start with the practical rule: use inheritance for a real type relationship, not just for code reuse. Then explain overriding and super(). Mention isinstance(), issubclass(), and the MRO. Finish by explaining that multiple inheritance follows the MRO and that composition is often better when the goal is only to use another object's behavior.
Interviewer may ask next
What happens if two base classes provide a method with the same name?
Python uses the method resolution order to determine which implementation is found first. With multiple inheritance, Python creates one consistent lookup sequence for the class and its base classes. A normal method lookup follows that sequence, and super() also continues through that sequence. This matters because changing the base class order can change the MRO and therefore change which implementation runs. Multiple inheritance can be useful, but its main tradeoff is that class relationships and cooperative method calls can become harder to understand.
When would you choose composition instead of inheritance?
I would choose composition when one object needs another object's behavior but is not truly a more specific form of that object. With composition, one object stores or receives another object and calls it when needed. This usually reduces coupling and makes the dependency easier to replace or test. Inheritance is useful when there is a meaningful type relationship and shared behavior belongs to that relationship. The main tradeoff is that inheritance gives convenient shared type behavior, while composition usually gives more flexibility.
3. How does cooperative multiple inheritance with super() work?Language SpecificHard
i Question Details
Explain zero-argument super(), how calls follow the method resolution order rather than a single parent, signature compatibility requirements, and why every participating class must delegate consistently.
Short Interview Answer (30-60 seconds)
Cooperative multiple inheritance works when each participating class performs its own task and then calls super() with compatible arguments. Zero argument super() does not simply call one direct parent. It continues with the next class in the method resolution order. This allows every class in the chain to run once. The design fails if a class stops the chain too early or passes arguments that the next method cannot accept.
Use cooperative multiple inheritance only when every participating class follows one shared calling contract. Inside an instance method, zero argument super() uses the class where the method was defined and the current instance. Python then searches after that class in the method resolution order, called the MRO. It does not simply choose one direct parent.
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?
For example, ReportProcessor can inherit from LoggingMixin, ValidationMixin, and BaseProcessor. ReportProcessor calls super(). LoggingMixin logs the request and delegates. ValidationMixin checks the data and delegates. BaseProcessor intentionally ends the chain. Each method runs once in MRO order.
Signatures must be compatible. A useful pattern is for each class to accept the named arguments it owns and pass remaining arguments through **kwargs. If a class does not call super(), later methods are skipped. If it forwards an unsupported argument, Python can raise TypeError.
The runtime cost is one method call and MRO lookup for each participating class. Zero argument super() creates a small proxy object, but it does not copy the instance or its data. The memory cost is normally small. In production, keep the hierarchy shallow, document the argument contract, inspect the MRO, and test the complete call chain.
Example
The example defines ReportProcessor, LoggingMixin, ValidationMixin, and BaseProcessor. Calling process on ReportProcessor starts the cooperative chain. Each zero argument super() call continues with the next class in ReportProcessor's MRO. LoggingMixin consumes request_id. ValidationMixin consumes is_valid. Both pass the remaining named arguments forward. BaseProcessor is the intentional endpoint and rejects any argument that no earlier class consumed. The printed MRO and messages show the exact lookup and execution order.
Code
classBaseProcessor:
defprocess(self, **kwargs):
# This class is the intentional endpoint of the cooperative chain.# Reject arguments that no earlier class consumed.if kwargs:
unexpected = ", ".join(sorted(kwargs))
raise TypeError(f"Unexpected arguments: {unexpected}")
print("Base processing complete")
classValidationMixin:
defprocess(self, *, is_valid, **kwargs):
# Consume the argument owned by this class.ifnot is_valid:
raise ValueError("The report is not valid")
print("Validation complete")
# Continue with the next class in the MRO.super().process(**kwargs)
classLoggingMixin:
defprocess(self, *, request_id, **kwargs):
# Consume the argument owned by this class.print(f"Logging request {request_id}")
# Continue with the next class in the MRO.super().process(**kwargs)
classReportProcessor(LoggingMixin, ValidationMixin, BaseProcessor):
defprocess(self, **kwargs):
print("Report processing started")
# Continue with LoggingMixin, the next class in the MRO.super().process(**kwargs)
if __name__ == "__main__":
processor = ReportProcessor()
# Show the exact order Python uses for method lookup.print([cls.__name__ for cls in ReportProcessor.mro()])
# Each mixin consumes its own named argument.
processor.process(request_id="REQ123", is_valid=True)
Where it is used
This pattern is used when small mixins add separate behavior to one operation. Common examples include validation, logging, access checks, serialization, and framework lifecycle methods. It works best when each class has one clear responsibility and all classes can follow the same method contract. Composition is usually clearer when behaviors need different arguments, independent state, or an explicit execution order.
Why Interviewers Ask This
Interviewers ask this question to check whether a candidate understands Python method resolution order, zero argument super(), and safe method design in a multiple inheritance hierarchy. They also evaluate whether the candidate can recognize fragile inheritance designs and choose composition when it is clearer.
Common interview mistakes
A common mistake is assuming super() always calls one direct parent. It actually continues after the current defining class in the MRO. Another mistake is calling a parent class by name, which can skip another class or cause one implementation to run more than once. The chain also breaks when an intermediate class forgets to call super(), uses an incompatible signature, removes an argument owned by another class, or forwards an unsupported argument. A terminal base may end the chain intentionally, but that endpoint should be clear and documented.
Interview tip
Begin by saying that super() follows the MRO rather than one direct parent. Then explain the two main rules: every intermediate class must delegate, and all participating methods must use compatible signatures. Finish with one small mixin example and mention that composition is often clearer for unrelated behaviors.
Interviewer may ask next
What happens if one intermediate class does not call super()?
The cooperative chain stops at that class. Every later process implementation in the MRO is skipped because Python continues only when the current method delegates with super(). This matters because required validation, logging, cleanup, or base behavior may never run. An intentional terminal base can stop the chain, but an intermediate class should not.
When should composition be preferred over cooperative multiple inheritance?
Composition should be preferred when the behaviors need different method contracts, own independent state, or require an explicit execution order. Cooperative inheritance can reduce repeated wiring for small compatible mixins, but composition usually makes dependencies and control flow easier to understand, replace, and test.
4. What is a package in Python?Language SpecificEasy
i Question Details
Explain how packages organize related modules, the role of package directories and __init__.py, and how packages differ from individual modules.
Short Interview Answer (30-60 seconds)
A package is a special kind of Python module that can contain related modules and smaller packages. In a typical project, it is represented by a directory. A regular package normally has an __init__.py file, which Python executes when it first imports the package. Python also supports namespace packages without that file. A module usually represents one unit of code, while a package provides a naming hierarchy for organizing several units.
Detailed Explanation
A package groups related Python modules under one import path. For example, a shop package may contain orders.py, payments.py, and products.py. Each file is normally an individual module. The shop package gives them names such as shop.orders and shop.payments.
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?
Technically, a package is a special kind of module with a __path__ attribute. This path tells Python where it can search for the package's submodules.
A regular package is typically a directory containing __init__.py. Python executes this file when the package is first imported. It may be empty, perform small initialization tasks, or expose selected names from other modules. Heavy work should be avoided there because it increases import time, keeps more objects in memory, and may create unwanted side effects.
Python also supports namespace packages without __init__.py. One namespace package can combine portions found in different import locations. This is useful for some large libraries, but it adds complexity.
Importing a package does not automatically import every module inside it. Loading all modules would increase startup work and memory use. Packages should therefore expose only the modules and names that users need.
Where it is used
Packages are used to divide production applications into clear areas such as authentication, payments, database access, API handling, and tests. Libraries use packages to provide stable import paths and group related public features. Smaller packages also help teams test, reuse, replace, and maintain parts of an application without placing all code in one large module.
Why Interviewers Ask This
Interviewers ask this question to check whether the candidate understands how Python organizes code and resolves imports. They also want to know whether the candidate can distinguish a package from an individual module, explain the role of __init__.py, and make sensible decisions about package structure in a production application.
Common interview mistakes
A common mistake is saying that a package is only a directory. A package is a module that Python recognizes as able to contain submodules, and it has a __path__ attribute. Another mistake is saying that every package must have __init__.py. Regular packages normally have this file, but namespace packages do not. Developers may also assume that importing a package automatically imports every module inside it. Python normally loads only the package and the modules requested by the import statements. Other mistakes include placing slow database or network work in __init__.py, exposing too many internal names, and creating circular imports between package modules.
Interview tip
Begin with the main difference. Explain that a package organizes related modules and is itself a special module that can contain submodules. Then describe regular packages, __init__.py, and namespace packages. Finish by noting that importing a package does not automatically import every module inside it.
Interviewer may ask next
Can a Python package work without an __init__.py file?
Yes. A namespace package works without an __init__.py file. Python creates it from matching package portions found in one or more import locations. This matters when one logical package must be spread across several directories or installed distributions. The tradeoff is greater import and packaging complexity, so a regular package is usually simpler when this feature is not required.
Should __init__.py import every module in the package?
No. __init__.py should import only the modules or names that the package intentionally exposes. Importing every module increases import time, creates additional module objects and related memory use, and may trigger unwanted side effects or circular imports. Selective imports provide a clearer public interface while avoiding unnecessary startup work.
5. What does enumerate() do?Language SpecificEasy
i Question Details
Explain how enumerate produces index-value pairs, how its start argument works, and why it is preferable to manually maintaining a loop counter.
Short Interview Answer (30-60 seconds)
enumerate() lets me loop over an iterable while receiving both a counter and the current value. It yields pairs containing the counter and the item. The counter starts at zero by default, but the start argument can change its first value. It is usually clearer and safer than maintaining a separate counter.
Detailed Explanation
Use enumerate() when a loop needs both a counter and the current item. It accepts an iterable, such as a list, tuple, string, or generator, and returns an enumerate iterator. As the loop requests values, the iterator yields tuples containing the current counter and the next item.
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?
For example, enumerate(["red", "blue"]) yields (0, "red") and then (1, "blue"). A loop can unpack each tuple into two names, such as position and color.
The counter starts at zero unless start is provided. enumerate(values, start=1) pairs the first item with one. The start argument does not skip items and does not change the original iterable.
enumerate() is usually better than a manual counter because it removes extra state. A manual counter can become incorrect when the loop changes, especially when continue causes an update statement to be skipped.
enumerate() works lazily. It does not create every pair in advance. Creating the iterator uses constant extra memory, and processing all items takes linear time. The iterator is consumed as it is read, so reuse requires creating a new enumerate object.
Where it is used
enumerate() is useful when numbering displayed results, reporting the position of invalid input, processing rows with row numbers, logging item positions, or updating a collection while also needing each current index. It is most appropriate when the loop needs both the value and a related counter.
Why Interviewers Ask This
Interviewers ask this question to check whether a candidate understands Python iteration, can write clear loops, and knows when to replace a manually updated counter with a built in tool.
Common interview mistakes
A common mistake is assuming that the counter is always the real index of a sequence. When start is not zero, the counter and the actual index differ. Another mistake is expecting start to skip items. It changes only the counter. Developers may also convert enumerate() to a list without needing every pair in memory, reuse an already consumed enumerate iterator, or maintain a separate counter when enumerate() would be clearer.
Interview tip
Explain three points clearly: enumerate() yields counter and value pairs, the default counter starts at zero, and start changes the counter without skipping items. Then mention that it avoids the extra state of a manual counter.
Interviewer may ask next
Does the start argument make enumerate() skip items?
No. The start argument changes only the first counter value. enumerate(values, start=5) still reads the first item first, but pairs it with five. This matters because the counter is produced alongside iteration and does not control which item is read.
When is range(len(values)) more suitable than enumerate(values)?
range(len(values)) can be more suitable when the numeric index itself is required for several index based operations, such as comparing nearby elements or coordinating multiple sequences. enumerate() is clearer when the loop mainly needs each item and its counter. The tradeoff is greater index control versus simpler and safer iteration.
6. How does Python manage memory, including reference counting and cyclic garbage collection?Language SpecificHard
i Question Details
Explain Python's private heap, reference counting, object deallocation, reference cycles, the cyclic garbage collector, and common causes of memory that remains reachable longer than expected.
Short Interview Answer (30-60 seconds)
In CPython, Python objects live in a private heap managed by the Python memory manager. Each object normally has a reference count. When that count reaches zero, CPython can usually destroy the object immediately. Reference counting alone cannot remove a group of objects that only reference each other. The cyclic garbage collector finds those unreachable cycles and clears them. Memory can still grow when objects remain reachable through globals, caches, containers, closures, tasks, or callbacks. Also, freed object memory may stay inside Python for reuse instead of returning to the operating system.
Detailed Explanation
The practical point is that CPython usually frees an object when nothing refers to it, but cycles need a second cleanup system. Memory can also remain allocated when objects are still reachable or when Python keeps freed blocks for reuse.
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 stores its objects and internal data structures in a private heap. Application code does not directly manage this heap. The Python memory manager requests larger areas of memory from the operating system and then uses internal allocators for Python objects. Different object types may use different allocation strategies because a small integer, a list, and a dictionary have different storage needs.
In normal CPython, reference counting is the first cleanup mechanism. Every object keeps track of how many active references point to it. Assigning an object to another variable usually increases that count. Removing a reference usually decreases it. When the count reaches zero, CPython can run the object cleanup process and release its owned resources immediately.
For example, a local list may become unreachable when a function returns. If no other object refers to that list, its reference count reaches zero. CPython can destroy the list and decrease the counts of the objects stored inside it.
Reference counting cannot solve every case. Two objects can reference each other. Their reference counts stay above zero even after the application loses all outside references. This is a reference cycle. A parent object may refer to a child, while the child refers back to the parent.
CPython therefore includes a cyclic garbage collector. It supplements reference counting. The collector tracks container objects that can participate in cycles, such as lists, dictionaries, class instances, and other objects that hold references. It periodically examines groups of tracked objects. If a group cannot be reached from live application roots, the group is garbage even when its internal reference counts are not zero. The collector can then clear the cycle and allow the objects to be destroyed.
The collector uses generations because most objects die young. New tracked objects are checked more often. Objects that survive collections move to older generations and are checked less often. This reduces the cost of scanning every tracked object during every collection.
An important production point is that memory growth does not always mean the collector is broken. An object cannot be collected while it is still reachable. Common causes include unbounded dictionaries, lists, caches, global variables, closures, registered callbacks, background tasks, retained exceptions, tracebacks, and application sessions that are never removed.
Memory shown by the operating system may also stay high after objects are destroyed. Python allocators often keep freed blocks and arenas so later allocations can reuse them quickly. Some object types also use free lists. Therefore, object memory becoming reusable inside Python does not always mean the process immediately returns that memory to the operating system.
For debugging, I first confirm that memory growth is real and repeatable. I use tracemalloc to compare allocation snapshots. I inspect cache sizes, container lengths, task registries, and object ownership. The gc module can show collection statistics and tracked objects, but forcing gc.collect is not a general fix. The correct fix is usually to remove the unwanted reference, bound the cache, close the resource, or correct the object lifecycle.
Technical Approach
First, identify where Python obtains and stores object memory. Python requests memory from the operating system and manages Python objects inside its private heap. Second, follow the reference count. Creating or storing another reference increases the count. Removing a reference decreases it. A count of zero normally allows immediate cleanup in CPython. Third, check for reference cycles. If objects only refer to each other, their counts may never reach zero. Fourth, let the cyclic garbage collector find tracked container groups that are no longer reachable. Fifth, separate unreachable garbage from reachable memory growth. Inspect globals, caches, containers, callbacks, tasks, closures, and tracebacks. Finally, remember that freed memory may remain in Python allocators for reuse, so process memory and live object memory are not always the same.
Practical Complexity & Trade-offs
Traditional algorithm complexity does not fully describe Python memory management. Reference count updates add a small cost when references are created or removed. Destroying a container can take time related to how many references it owns. Cyclic collection costs depend on how many tracked objects the collector examines. Large object graphs can therefore cause longer collection work. Generations reduce this cost by checking young objects more often and older objects less often. Keeping many reachable objects increases memory use and can make scans more expensive. Python allocators improve speed by reusing freed memory, but the process may keep a larger memory footprint. The practical goal is to control object lifetime and measure real allocation growth.
Where it is used
This knowledge is useful in long running web services, task workers, data pipelines, notebooks, machine learning jobs, and applications that process large files. It helps when designing bounded caches, closing database and file resources, removing completed tasks, and cleaning callback registrations. It is also useful when diagnosing a worker whose memory grows after every job. Developers use tracemalloc, allocation snapshots, object counts, cache metrics, and garbage collector statistics to locate the retaining reference. Understanding reachability also helps when using closures, class relationships, event systems, and dependency containers.
Why Interviewers Ask This
Interviewers ask this question to check whether the candidate understands what happens after Python creates an object. A strong answer separates the private heap, object allocation, reference counting, and cyclic garbage collection. It also shows practical judgment about memory growth. The candidate should know that an object can remain in memory because it is still reachable, even when the program no longer needs it. This matters when debugging long running services, workers, data pipelines, and applications with large caches.
Common interview mistakes
A common mistake is saying that Python uses only garbage collection. Normal CPython mainly uses reference counting and adds cyclic collection for unreachable cycles. Another mistake is saying that del deletes an object. The del statement removes one reference. The object remains alive if another reference still exists. Candidates also assume that every increase in process memory is a leak. Python may keep freed memory for reuse. Another mistake is calling gc.collect repeatedly instead of finding the retaining reference. Disabling the cyclic collector without proving that cycles cannot occur is also risky. Finally, a weak reference does not keep its target alive, but using weak references everywhere is not a substitute for correct ownership.
Interview tip
Explain the answer in four parts. Start with the private heap. Then explain reference counting and immediate cleanup. Next, show why a cycle defeats reference counting and how the cyclic collector handles it. Finish with the production distinction between unreachable garbage, reachable objects, and memory retained by Python allocators. Mention tracemalloc as a practical debugging tool. Do not claim that del directly frees an object or that every high memory value is a leak.
Interviewer may ask next
Why can memory keep growing even when the cyclic garbage collector is running?
The most common reason is that the objects are still reachable. The collector removes unreachable cycles. It cannot remove an object that a live global, cache, list, dictionary, closure, callback, task registry, traceback, or session still references. This is often called logical retention rather than unreachable garbage. I would inspect which objects grow and then find who refers to them. Tracemalloc can compare allocation snapshots and identify the code paths creating memory. Application metrics can also show cache size, active sessions, queued tasks, and container length. Another reason is allocator behavior. After objects are destroyed, Python may keep freed blocks and arenas for later reuse. The operating system can therefore show a large process even when Python has fewer live objects. The exact fix depends on the cause. I would remove the unwanted reference, bound the collection, expire cache entries, close completed tasks, or redesign the object lifecycle. Repeatedly forcing collection does not fix reachable retention.
What exactly does del do, and when is an object actually destroyed?
The del statement removes a binding or container reference. It does not directly destroy the object. If other references still point to the same object, that object remains alive. In normal CPython, removing the final strong reference usually reduces the reference count to zero. CPython can then run finalization and release the object immediately. If the object belongs to a reference cycle, its count may stay above zero even when no application root can reach it. The cyclic collector must identify and clear that unreachable cycle. Weak references behave differently because they do not keep the target alive. When the final strong reference disappears, a weak reference no longer returns the object. The important interview point is to separate a variable name from the object itself. Names and containers hold references. The object lifetime depends on all strong references, not on one particular variable.
7. What is structural pattern matching in Python?Language SpecificMedium
i Question Details
Explain match and case semantics, literal, sequence, mapping, class, OR, capture, and wildcard patterns, guards, and the difference between pattern matching and a simple switch statement.
Short Interview Answer (30-60 seconds)
Structural pattern matching lets Python inspect both the value and the structure of an object with match and case. Python evaluates the subject once, checks cases from top to bottom, and runs the first case whose pattern matches and whose guard is true. Patterns can test literals, sequences, mappings, classes, alternatives, and nested data while capturing useful parts. This makes it more powerful than a simple switch statement.
Use structural pattern matching when a program must handle several clear shapes of data. It is available from Python 3.10.
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?
A match statement evaluates its subject once. Python checks each case in order. It runs the first case whose pattern succeeds and whose optional guard is true.
A literal pattern checks a fixed value. A sequence pattern checks and extracts items from supported sequence objects, but it does not treat strings, bytes, byte arrays, or iterators as sequence patterns. A mapping pattern checks required keys and ignores extra keys unless the pattern captures them. A class pattern uses an instance check and can inspect attributes. An OR pattern accepts any listed alternative. A capture pattern binds a value to a name. The underscore wildcard accepts any value without binding it. A guard adds an extra if condition after the pattern succeeds.
This is more than a simple switch. It can inspect nested structure and extract data instead of only comparing one value with fixed choices.
Use it for structured commands, events, parsed data, and domain objects. Avoid it when a small if and elif chain is clearer. Put specific patterns before broad capture or wildcard patterns.
Example
The example matches one value named event. It demonstrates literal, OR, sequence, mapping, class, capture, wildcard, and guard patterns. Python evaluates event once and checks the cases from top to bottom. The first case whose pattern succeeds and whose guard is true returns a result. The specific payment cases appear before the general mapping case so they are not hidden by a broader pattern. The final wildcard handles every value that earlier cases do not handle.
Code
from dataclasses import dataclass
from typing importAny@dataclassclassUserEvent:
# A dataclass supports class patterns for its fields.
name: str
active: booldefdescribe_event(event: Any) -> str:
# Python evaluates event once and checks each case in order.match event:
# Literal pattern: match the exact singleton value None.caseNone:
return"No event"# OR pattern: accept either literal command.case"start" | "begin":
return"Start command"# Sequence pattern: require exactly three items.# The values in the second and third positions are captured.case ["move", x, y]:
returnf"Move to {x}, {y}"# Mapping pattern with a guard.# Extra mapping keys are allowed and ignored here.case {"type": "payment", "amount": amount} if amount > 0:
returnf"Valid payment of {amount}"# The pattern still matches when the amount is not positive.# This case runs after the guard above is false.case {"type": "payment", "amount": amount}:
returnf"Invalid payment amount: {amount}"# Class pattern: check the object type and inspect attributes.case UserEvent(name=name, active=True):
returnf"Active user: {name}"# Capture pattern: store the value of the type key.case {"type": event_type}:
returnf"Other event type: {event_type}"# Wildcard pattern: accept anything not handled above.# The underscore does not bind a new variable.case _:
return"Unknown event"if __name__ == "__main__":
examples = [
None,
"start",
["move", 10, 20],
{"type": "payment", "amount": 50, "currency": "USD"},
{"type": "payment", "amount": 0},
UserEvent(name="Asha", active=True),
{"type": "logout"},
42,
]
# Run every example so this file can be copied and executed.for example in examples:
print(describe_event(example))
Where it is used
Structural pattern matching is useful for processing structured API responses, application commands, event messages, parsed syntax trees, configuration records, and domain objects with several known forms. It works well when each data shape has a clear action and useful values must be extracted. Its runtime cost depends on the patterns used, including length checks, equality checks, mapping lookups, instance checks, and attribute access. Matching normally binds references instead of copying the whole subject. A starred sequence capture creates a new list, and a double star mapping capture creates a new dictionary for the remaining items.
Why Interviewers Ask This
Interviewers ask this question to check whether a candidate understands modern Python syntax, case selection, data extraction, guards, and pattern order. It also tests whether the candidate can choose pattern matching when it improves clarity instead of treating it as a direct replacement for every if statement.
Common interview mistakes
A common mistake is placing a broad capture or wildcard pattern before specific cases. An unguarded capture or wildcard pattern always succeeds, so Python requires an irrefutable case to be last. Another mistake is treating a plain name in a pattern as a constant. A plain name is normally a capture pattern, while a named constant must normally use a qualified name such as Color.RED. Developers may also expect sequence patterns to match strings or iterators, but they do not. Other mistakes include assuming mapping patterns reject extra keys, forgetting that guards run only after a pattern succeeds, and relying on variable bindings produced during a failed pattern because that behavior is not guaranteed.
Interview tip
Start with the runtime rule: Python evaluates the subject once, checks cases in order, and selects the first pattern with a true guard. Then explain that patterns can inspect structure and capture values. Give one mapping or sequence example. Finish by saying that match is more powerful than a simple switch but should be used only when it makes structured decisions clearer.
Interviewer may ask next
What happens when a pattern matches but its guard is false?
Python skips that case body and continues checking later cases because a case is selected only when both its pattern succeeds and its guard is true. Values needed by the guard are captured before the guard runs. Guard expressions can raise exceptions or cause side effects, so production code should keep them simple and predictable.
What performance and memory costs can structural pattern matching add?
The cost depends on the exact pattern because Python may perform equality checks, length checks, mapping lookups, instance checks, and attribute access. There is no single complexity for every match statement. Ordinary captures bind references and do not copy the complete subject. However, a starred sequence capture builds a new list, and a double star mapping capture builds a new dictionary, so those forms use additional memory.
8. What are Python's main built-in data types?Language SpecificEasy
i Question Details
Identify the main numeric, sequence, mapping, set, Boolean, binary, and null-value types, and give an appropriate use case for each group.
Short Interview Answer (30-60 seconds)
Python has several main groups of built in data types. Numeric types include int, float, and complex. Sequence types include str, list, tuple, and range. dict is the main mapping type. set and frozenset store unique values. bool represents True or False. bytes, bytearray, and memoryview handle binary data. NoneType contains the single value None, which represents no value. I choose a type based on ordering, uniqueness, lookup needs, mutability, and the kind of data being stored.
Detailed Explanation
Python groups its main built in types by the kind of value they represent. Numeric types are int for whole numbers, float for binary floating point values, and complex for numbers with real and imaginary parts. bool represents True or False. It is a separate Boolean type, although it is also a subclass of int.
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?
Sequence types keep items in order. str stores text. list stores items that may change. tuple stores a fixed group of items. range represents an immutable sequence of integers without storing every integer as a normal list.
dict is the main mapping type. It stores values by unique, hashable keys. set stores unique mutable members, while frozenset is immutable. Dictionary and set membership checks are usually fast on average, but their exact cost can grow in unusual collision cases.
Binary types include bytes for immutable binary data, bytearray for mutable binary data, and memoryview for viewing buffer data without copying the underlying bytes. A memoryview object still uses a small amount of memory.
NoneType has one value, None. It represents the absence of a value. The correct type depends on ordering, uniqueness, lookup needs, mutability, exact numeric requirements, and whether data should be copied.
Where it is used
Numeric types are used for counts, measurements, calculations, and scientific values. Strings store names, messages, and text content. Lists store ordered collections that may change, such as queued tasks. Tuples store fixed groups, such as coordinates or database rows. Ranges are useful for loops because they represent integer sequences without creating a full list. Dictionaries store configuration, API records, and values that must be found by key. Sets remove duplicates and support fast membership checks. Booleans control conditions and feature flags. Bytes and bytearrays handle files, network messages, and encoded data. Memoryview is useful when large binary buffers must be accessed without copying the underlying data. None represents a missing value or a function result that has no useful value.
Why Interviewers Ask This
Interviewers ask this question to check whether a candidate understands the basic values available in Python and can choose an appropriate type for real code. They also evaluate whether the candidate understands ordering, uniqueness, mutability, hashing, binary data, missing values, and practical performance tradeoffs.
Common interview mistakes
A common mistake is thinking that every collection can be changed. Lists, dictionaries, sets, and bytearrays are mutable. Strings, tuples, ranges, bytes, frozensets, numbers, booleans, and None are immutable. Another mistake is using a list, dictionary, or set as a dictionary key. Dictionary keys must be hashable, so mutable built in collections cannot normally be keys. Candidates may also confuse None with False, zero, or an empty string. These values are all falsy in conditions, but they are different values with different meanings. Another mistake is using float when an exact decimal result is required, such as some money calculations. Binary floating point cannot represent every decimal fraction exactly. It is also incorrect to assume that memoryview uses no memory. It avoids copying the underlying buffer, but the memoryview object itself still requires memory.
Interview tip
Group the types by purpose instead of giving one long list. Name each group, give one practical use, and explain the important differences in ordering, uniqueness, mutability, hashing, and memory behavior.
Interviewer may ask next
Why can a tuple sometimes be a dictionary key while a list cannot?
A tuple can be a dictionary key only when every value inside it is also hashable. A list cannot be a dictionary key because it is mutable and unhashable. Dictionary keys need a stable hash value while they are stored. This matters because changing a key after insertion could prevent Python from finding the stored value correctly.
What is the tradeoff between using a list and a set for membership checks?
A set usually provides constant time membership checks on average, while a list may need to examine each item and therefore takes linear time. A set is a good choice when uniqueness and frequent membership checks matter. The tradeoff is that a set does not support position based access, does not preserve duplicate values, and commonly uses more memory because it maintains a hash table.
9. What is the difference between mutable and immutable objects?Language SpecificEasy
i Question Details
Explain mutation versus rebinding, classify common built-in objects, and describe why mutability matters when values are shared, passed to functions, or used as dictionary keys.
Short Interview Answer (30-60 seconds)
Mutable objects can change after they are created, while immutable objects cannot. Lists, dictionaries, sets, and bytearrays are mutable. Integers, floats, strings, bytes, tuples, and frozensets are immutable. Mutating a list changes the same object, so every variable that references it can see the change. An operation on an immutable value produces another value and the variable is rebound. This matters when objects are shared, passed to functions, copied, or used as dictionary keys.
Detailed Explanation
The practical difference is that a mutable object can change in place, while an immutable object cannot. A list is mutable, so append changes the list. A string is immutable, so concatenation produces a new string object and rebinds the variable.
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 variables hold references to objects. If two variables reference the same list, a change through one variable is visible through the other. Passing that list to a function gives the function access to the same object. Reassigning the parameter only changes the local name and does not rebind the caller's variable.
Mutable built in objects include lists, dictionaries, sets, and bytearrays. Immutable objects include integers, floats, booleans, strings, bytes, tuples, and frozensets. A tuple cannot replace its elements, but an element may reference a mutable object whose contents can still change.
Dictionary keys and set elements must be hashable. Immutability often supports stable hashing, but it does not guarantee hashability. A tuple is hashable only when every contained value is hashable.
Mutation can avoid allocating a replacement container. Operations on immutable values may allocate a new object and copy data. Repeated string concatenation can use extra time and memory. Control mutation when data is shared, cached, or reused.
Where it is used
Mutability matters when request data moves through service functions, when lists or dictionaries are cached, when configuration data is shared, and when several objects reference the same collection. It also matters when choosing dictionary keys, designing function interfaces, copying nested data, and preventing one part of an application from changing data owned by another part. Immutable values are useful for stable identifiers and safely shared values. Mutable values are useful when data must be updated efficiently, but ownership and copying rules should be clear.
Why Interviewers Ask This
Interviewers ask this question to check whether a candidate understands Python object identity, references, mutation, rebinding, function arguments, hashing, and safe data structure choices. It also tests whether the candidate can predict when a change will affect other parts of a program.
Common interview mistakes
A common mistake is thinking that passing an object to a function creates a copy. Python passes the same object reference to the parameter. Another mistake is confusing mutation with rebinding. Appending to a list changes the object, while assigning a new list changes which object a name references. Developers may also use a mutable object as a default function argument, which can share state across calls. Another mistake is assuming every immutable object is hashable. A tuple is unhashable when any contained value is unhashable. Shallow copying is also often misunderstood because nested mutable objects can still be shared.
Interview tip
Begin by saying that mutable objects can change in place and immutable objects cannot. Give a list and a string as examples. Then explain shared references, mutation versus rebinding, function arguments, and the hashability rule for dictionary keys. Mention the tuple containing a list as the main edge case.
Interviewer may ask next
Can an immutable tuple contain a mutable object?
Yes. The tuple cannot replace, add, or remove its element references, but an element may reference a mutable object such as a list. The list contents can still change. This matters because the tuple is not hashable when it contains an unhashable object, so it cannot be used as a dictionary key or set element.
Why should mutable default function arguments be avoided?
A mutable default object is created once when the function definition runs, not once for every call. If one call changes that object, a later call can see the earlier change. This creates unexpected shared state. The usual production choice is to use None as the default and create a new list or dictionary inside the function. The tradeoff is a small allocation for each call in exchange for predictable behavior.
10. What is the difference between == and is?Language SpecificEasy
i Question Details
Explain value equality versus object identity, when an identity comparison is appropriate, and why None is normally checked with is.
Short Interview Answer (30-60 seconds)
Use == when you want to compare values. Use is when you want to check whether two names refer to the exact same object. I use is mainly for singleton objects such as None and for private sentinel objects. I do not use is to compare normal strings, numbers, or collections.
The practical rule is simple. Use == for value equality. Use is for object identity.
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?
The == operator asks whether two objects are equal according to their comparison rules. Built in types such as lists compare their contents. Two different lists can therefore be equal when they contain equal items.
The is operator asks whether both operands refer to the exact same object. It does not compare the contents of that object. Python defines x is y as true only when x and y are the same object. ([docs.python.org](https://docs.python.org/3.15/reference/expressions.html?utm_source=chatgpt.com))
For example, first = [1, 2] and second = [1, 2] create separate list objects. first == second is True because their values are equal. first is second is False because they are not the same list. If alias = first, then first is alias is True because both names refer to one list.
A type can customize == through its equality method. Because of that, == may perform content checks, call user code, return NotImplemented so Python can try another comparison path, raise an exception, or even return a non Boolean object. Built in comparisons normally return True or False. In a condition, Python converts a custom comparison result to a truth value. The is operator cannot be customized. It always checks identity.
None should normally be checked with is None or is not None. None is the sole instance of NoneType, and Python style guidance says singleton comparisons should use identity rather than equality. ([docs.python.org](https://docs.python.org/3/library/constants.html?utm_source=chatgpt.com)) This also avoids custom equality behavior. An object can define == in a way that reports equality with None, but it cannot make itself identical to None.
Identity comparison is also useful for a private sentinel object. A sentinel is one unique object used as a special marker. It is helpful when None is already a valid input and the program must distinguish None from an argument that was not provided.
Do not use is to compare normal numbers or strings. A Python implementation may reuse some objects, so an identity comparison may appear to work in one case and fail in another. That reuse is an implementation detail and is not a valid rule for value comparison.
One edge case is a not a number value. A floating point NaN is not equal to itself, so nan_value == nan_value is False. However, nan_value is nan_value is True when both names refer to the same NaN object. This shows that equality and identity answer different questions.
In production code, use == for business values, request data, database results, strings, numbers, collections, and domain objects when logical equality is intended. Use is for None, private sentinels, and rare cases where the exact object matters.
Key Insight / Why This Solution Works
First, decide which question the program must answer.
If the program needs to know whether two values are logically equal, use ==.
If the program needs to know whether two names refer to the exact same object, use is.
For an optional value, write value is None or value is not None.
When None is a valid value and a separate missing marker is needed, create one sentinel with object() and compare it using is.
Do not choose is because two small numbers or strings happen to share an object during one test. That behavior is not a reliable value comparison rule.
Remember that custom equality code can change the behavior and cost of ==. The meaning of is does not change.
Example
The code creates two different lists with equal contents and one alias that refers to the first list. It shows that == compares the list values while is checks whether the references identify one object. It then uses is for a None check and a private sentinel check. The final class demonstrates that custom equality can report equality with None, while identity still correctly reports that the object is not None.
Code
classAlwaysEqual:
def__eq__(self, other):
returnTruedefmain():
first = [1, 2]
second = [1, 2]
alias = first
print(first == second) # True because the list values are equalprint(first is second) # False because they are different list objectsprint(first is alias) # True because both names refer to one object
value = Noneprint(value isNone) # True
missing = object()
result = missing
print(result is missing) # True
unusual_value = AlwaysEqual()
print(unusual_value == None) # True because custom equality returns Trueprint(unusual_value isNone) # False because it is not the None objectif __name__ == "__main__":
main()
Where it is used
The == operator is used when validating user input, comparing API fields, checking database values, verifying test results, comparing collections, and deciding whether two domain objects represent the same logical value. The is operator is used for None checks, private sentinel checks, and cases where code must confirm that two references point to one exact mutable object. A common production example is an optional function argument. If None is a valid argument, the function can create one private sentinel object to represent an argument that was not supplied. Identity comparison keeps that marker separate from every valid value.
Why Interviewers Ask This
Interviewers ask this question to check whether a candidate understands value equality, object identity, custom equality behavior, and the correct way to check for None. It also shows whether the candidate avoids relying on object reuse that may differ between Python implementations or execution contexts.
Common interview mistakes
A common mistake is using is to compare strings or numbers. Object reuse can make the result appear correct in one test, but the code is checking identity rather than value.
Another mistake is writing value == None. It may produce the expected result for many built in values, but a custom equality method can change the result. value is None states the intended identity check clearly.
Some developers assume that equal objects must be the same object. Two separately created lists or class instances can be equal while having different identities.
Another mistake is assuming that == always performs a simple or cheap comparison. It may scan a large collection or execute custom Python code.
A final mistake is using id values as permanent memory addresses. Identity remains stable only during an object's lifetime, and an id value may be reused after that object is destroyed.
Interview tip
Begin with the rule that == compares values and is checks identity. Use two equal but separate lists as the example. Then explain that None is a singleton and should normally be checked with is None. Mention object reuse only as a warning, not as behavior that application code should depend on.
Interviewer may ask next
Can x is y be True while x == y is False?
Yes, custom or special equality behavior can make this possible. A NaN object is a standard example. If x refers to one floating point NaN object and y = x, then x is y is True because both names identify the same object, while x == y is False because NaN is not equal to itself. This matters because identity does not guarantee logical equality for every possible value.
When should a private sentinel be used instead of None?
Use a private sentinel when None is a valid input and the program also needs a separate marker for a missing argument. Create one object, keep its reference, and compare values with is. The tradeoff is that a sentinel adds one more special value that developers must understand, but it avoids confusing a real None value with an omitted value.
More questions load as you scroll
Python Developer Resume Examples
Explore the resume examples below to find the one that best matches your target Python Developer role.
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.