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. What is the difference between a set and a frozenset in Python?Language SpecificEasyGoogle
i Question Details
Compare mutability, hashability, supported operations, and use as dictionary keys or set members.
Short Interview Answer (30-60 seconds)
Use a set when the collection must change, and use a frozenset when it must remain fixed. A set supports methods such as add, remove, and update, but it is not hashable. A frozenset has no mutation methods and is hashable because all of its elements must also be hashable. Therefore, a frozenset can be a dictionary key or a member of another set, while a normal set cannot.
Detailed Explanation
The practical difference is that a set can change after creation, while a frozenset cannot. Both store unique hashable elements. Both support membership tests, iteration, union, intersection, difference, and symmetric difference.
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 set also provides mutation methods such as add, discard, remove, clear, and update. A frozenset does not provide these methods. Operations on a frozenset create a new collection instead of changing the existing one. For a mixed operation between a set and a frozenset, the result type normally follows the object on the left side.
A set cannot be a dictionary key or a member of another set because it is mutable and unhashable. A frozenset can be used in those places because its membership cannot change and its elements are hashable.
Use a set for changing data such as active permissions or collected identifiers. Use a frozenset for fixed groups, cache keys, or unordered combinations. Membership tests are usually constant time on average for both types. Building or converting either collection takes time and memory proportional to the number of unique elements. Converting a set to a frozenset creates a new container but does not copy the element objects themselves.
Where it is used
Sets are useful for changing collections such as unique user identifiers, enabled features, visited records, and current permissions. Frozensets are useful for fixed permission groups, cache keys, unordered dictionary keys, graph relationships, and storing one unique group inside another set.
Why Interviewers Ask This
Interviewers ask this question to check whether a candidate understands mutability, hashability, set operations, and the rules used by Python hash based containers. It also tests whether the candidate can choose the correct collection when values must change or must be used as a dictionary key or set member.
Common interview mistakes
A common mistake is thinking that frozenset makes the objects inside it immutable. Only membership in the collection is fixed. Every element must still be hashable. Another mistake is trying to place a normal set inside a set or use it as a dictionary key, which raises a TypeError. Developers may also expect add, remove, or update methods on a frozenset. Another mistake is relying on iteration order. Neither set type should be used when order is part of the required result. It is also unsafe to use an object whose hash or equality behavior changes while it is stored in a hashed collection.
Interview tip
Start with mutability. Then connect mutability to hashability. Finish with the practical rule that a frozenset can be a dictionary key or set member, while a normal set cannot.
Interviewer may ask next
Can a frozenset contain a list or a normal set?
No. Every frozenset element must be hashable. Lists and normal sets are mutable and unhashable, so adding either one during construction raises a TypeError. A tuple or another frozenset can be used when all nested elements are also hashable. This matters because the outer frozenset needs valid and consistent hash behavior.
What is the tradeoff when converting a set to a frozenset for use as a dictionary key?
The conversion creates a new frozenset and takes time proportional to the number of elements. The new container also requires memory proportional to its number of unique elements, although it stores references rather than copying the element objects. The benefit is an immutable and hashable representation of an unordered group. The tradeoff is conversion work, additional container memory, and the loss of mutation methods.
2. What is truth-value testing in Python?Language SpecificEasyGoogle
i Question Details
Explain how objects become truthy or falsy through built-in rules, __bool__, and __len__, including common edge cases.
Short Interview Answer (30-60 seconds)
Truth value testing is how Python decides whether an object acts as true or false in a condition. None, False, numeric zero, and empty containers are falsy. Most other objects are truthy. For a custom object, Python checks __bool__ first and then __len__ when __bool__ is absent. In production code, I use value is None when zero or an empty value must remain valid.
Detailed Explanation
Truth value testing is how Python decides whether an object acts as true or false in if, while, not, bool, and Boolean expressions. None, False, numeric zero, and empty containers are falsy. Most other objects are truthy.
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 a custom object, Python first calls __bool__ if the class defines it. That method must return True or False. Returning another type raises TypeError. If __bool__ is absent, Python calls __len__. A length of zero means false. A positive length means true. A negative length raises ValueError. If neither method exists, the object is truthy by default. Any exception raised during the test is passed to the caller.
Use a simple truth test when missing and empty values should follow the same path. Use value is None when None has a separate meaning from zero, False, or an empty container. The cost of the test depends on the method that runs. Built in length checks are normally constant time, but custom methods can do expensive work. Truth testing normally uses constant extra memory and does not copy the object. Custom truth methods should therefore be fast, predictable, and free from side effects.
Where it is used
Truth value testing is used in input validation, request handling, configuration checks, collection processing, loops, guard conditions, and optional return values. For example, if records can detect an empty result collection. When zero, False, or an empty collection is valid data, an explicit check such as records is None prevents valid values from being treated as missing.
Why Interviewers Ask This
Interviewers ask this question to check whether a candidate understands how Python evaluates objects in conditions. It tests knowledge of built in falsy values, special method lookup, exception behavior, and the difference between testing truth and testing specifically for None.
Common interview mistakes
A common mistake is using if value when the code only wants to detect None. That test also rejects zero, False, empty strings, and empty containers. Another mistake is assuming and and or always return True or False. They return one of their operands after short circuit evaluation. Custom classes are also incorrect when __bool__ returns a value that is not a bool or when __len__ returns a negative number. Slow work, state changes, or external calls inside these methods can make ordinary conditions expensive or surprising.
Interview tip
Start with the common falsy values. Then give the exact order of __bool__, __len__, and the default truthy result. Finish by explaining why value is None is safer when other falsy values are valid.
Interviewer may ask next
What happens if __bool__ returns 1 instead of True?
Python raises TypeError because __bool__ must return an actual bool object. The integer 1 is normally truthy, but it is not accepted as the return value of __bool__. This strict behavior keeps custom truth testing predictable.
What is the performance cost of truth testing a custom object?
The cost is the cost of the selected __bool__ or __len__ method. A simple method is normally constant time and uses constant extra memory, but Python does not guarantee that a custom implementation is cheap. Expensive work or side effects inside the method can slow every condition that tests the object, so production implementations should remain small and predictable.
3. How are bytes, bytearray, and str different in Python?Language SpecificEasyGoogle
i Question Details
Compare text and binary data, mutability, encoding and decoding, indexing behavior, and suitable use cases.
Short Interview Answer (30-60 seconds)
Use str for text, bytes for fixed binary data, and bytearray for binary data that must change in place. A str contains Unicode characters. bytes and bytearray contain integer byte values from 0 to 255. I use encode to convert str to bytes and decode to convert binary data back to str.
Detailed Explanation
Use str for human readable text. A str contains Unicode characters and is immutable, so changing it creates a new object. Indexing a str returns a one character str.
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?
Use bytes for fixed binary data, such as file content, network messages, images, or encoded text. bytes is immutable. Indexing bytes returns an integer from 0 to 255, not a one byte bytes object.
Use bytearray when binary data must be edited in place. It stores the same byte values as bytes, but it is mutable. An indexed position can be replaced with an integer from 0 to 255.
Calling text.encode("utf8") creates bytes. Calling data.decode("utf8") creates str. The encoding must match the real data. Otherwise decoding can raise UnicodeDecodeError. Some binary data is not text and should never be decoded.
Repeated changes to str or bytes usually allocate new objects and copy data. bytearray can reduce that work when many binary updates are required. However, converting between these types still creates new objects. In production, keep application text as str and encode or decode only at clear system boundaries.
Where it is used
str is used for user input, names, messages, JSON text, database text, and logs. bytes is used for socket data, HTTP bodies, file content, compressed data, encoded text, and cryptographic values. bytearray is useful for building binary packets, editing file buffers, receiving data into reusable buffers, and changing selected byte values without creating a new binary object after every update.
Why Interviewers Ask This
Interviewers ask this question to check whether a candidate can separate text from binary data. They also test knowledge of mutability, character encoding, indexing behavior, object allocation, and safe data handling at file, network, and system boundaries.
Common interview mistakes
Common mistakes include joining or comparing str and bytes directly, assuming bytes indexing returns a one byte bytes object, and trying to modify bytes in place. Another mistake is decoding binary data with the wrong encoding or assuming every byte sequence represents text. Developers may also use bytearray for normal text processing, even though str is the correct type for text.
Interview tip
Start with the practical rule: str is text, bytes is fixed binary data, and bytearray is editable binary data. Then explain mutability, indexing results, and encode and decode. Finish with one production example, such as converting text before sending it through a socket.
Interviewer may ask next
What happens when you index or slice bytes, bytearray, and str?
Indexing str returns a one character str. Indexing bytes or bytearray returns an integer from 0 to 255. A slice returns the same general type as the source, so a bytes slice returns bytes, a bytearray slice returns bytearray, and a str slice returns str. The slice creates a new object, which matters when large binary data is copied repeatedly.
When should you choose bytearray instead of bytes?
Choose bytearray when binary content must be changed many times in place. The exact difference is mutability. bytearray supports indexed assignment and other updates, while changing bytes requires creating a new object. This can reduce repeated allocation and copying. The tradeoff is that bytearray is not hashable, so it cannot be used as a dictionary key, while bytes can.
4. How do Python's range objects behave?Language SpecificEasyGoogle
A Python range is an immutable sequence of integers that stores its start, stop, and step rules instead of storing every value. The start is included, the stop is excluded, and the step cannot be zero. Integer membership, indexing, and slicing are efficient, and slicing returns another range. Converting a large range to a list removes its memory advantage.
Detailed Explanation
Use range when you need a predictable integer sequence without building a full list. For example, range(2, 10, 2) represents 2, 4, 6, and 8. The start is included. The stop is excluded. The step controls the direction and distance between values. A zero step raises ValueError.
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 range is an immutable and reusable sequence, not an iterator. It stores only start, stop, and step information, so its memory use does not grow with the number of represented values. Very large integer arguments may still require more space to store those integers.
For integer values, membership testing uses bounds and arithmetic instead of scanning the sequence. Index access is also calculated directly. Slicing returns another range, so range(2, 10, 2)[1:3] represents 4 and 6 without creating a list.
Negative steps support descending sequences. For example, range(10, 2, -2) represents 10, 8, 6, and 4. A step that moves away from the stop creates an empty range. Use list(range(...)) only when stored or mutable values are truly required.
Where it is used
Range objects are commonly used for counted loops, index based processing, page numbers, retry limits, batch identifiers, test cases, and reverse iteration. They are a good production choice when values follow a regular integer pattern and do not need to be stored or changed. A list is more suitable when code must mutate values, keep independently created elements, or call an interface that specifically requires a list.
Why Interviewers Ask This
Interviewers ask this question to check whether a candidate understands Python sequence rules, exclusive stop values, step direction, slicing, membership behavior, and memory efficient iteration. It also tests whether the candidate knows that range is an immutable reusable sequence object, not an iterator and not a stored list of every value.
Common interview mistakes
Common mistakes include expecting the stop value to be included, passing a zero step, using a step that moves away from the stop, and assuming that range stores every represented integer. Another mistake is calling range an iterator. A range is a reusable sequence that creates an iterator when iteration begins. Developers may also assume that slicing returns a list or convert a very large range to a list without considering the resulting allocation. Range arguments must be integers or objects that provide an integer index value, so ordinary float arguments are not accepted.
Interview tip
Begin by saying that range is an immutable reusable integer sequence that stores rules rather than all values. Then explain included start, excluded stop, step direction, arithmetic membership testing, slicing to another range, and the allocation caused by converting it to a list.
Interviewer may ask next
What happens when the step direction cannot reach the stop value?
Python creates an empty range. For example, range(2, 10, -2) uses a negative step even though the stop is greater than the start, so the values move away from the stop. Python does not raise an error for this direction mismatch. Iterating over that range produces no values, and a loop using it runs zero times.
When should a range be converted to a list?
Convert a range to a list only when concrete stored values or list mutation are required. For the shared example, list(range(2, 10, 2)) creates the list containing 2, 4, 6, and 8. The benefit is access to list specific operations and mutable storage. The tradeoff is that time and memory now grow with the number of created integers, while the original range stores only its sequence rules.
5. How do bytes encode and decode Unicode text in Python?Language SpecificEasyGoogle
i Question Details
Explain character encodings, encode(), decode(), error strategies, and why text and bytes should not be mixed implicitly.
Short Interview Answer (30-60 seconds)
The practical rule is to keep text as str inside the program and convert only at system boundaries. I call encode with an explicit character encoding, usually UTF 8, to convert str into bytes. I call decode with the matching encoding to convert bytes back into str. Python keeps these types separate because it cannot safely guess which encoding should be used.
Detailed Explanation
The practical rule is to keep text as str inside the application and convert it only at input and output boundaries. A str contains Unicode characters. A bytes object contains raw integer byte values. Calling text.encode("utf 8") reads the characters and creates a new bytes object. Calling data.decode("utf 8") reads the byte sequence and creates a new str object.
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 decoder must use the encoding defined by the file format, protocol, or system contract. Using the wrong encoding may raise UnicodeDecodeError. It may also produce incorrect text if that encoding accepts the same bytes.
The default error strategy is strict. Encoding can raise UnicodeEncodeError when the selected encoding cannot represent a character. Decoding can raise UnicodeDecodeError for an invalid byte sequence. Other strategies include replace, ignore, backslashreplace, and surrogateescape. Each changes behavior, so production code should choose one deliberately. Ignore can silently lose data.
Python does not implicitly convert between str and bytes because there is no universally correct encoding. Encoding and decoding normally take time proportional to the input length and allocate a new result object. Decode incoming bytes once, process str internally, and encode once when sending or storing data.
Where it is used
This behavior is used when reading or writing text files, handling HTTP request and response bodies, communicating through sockets, processing subprocess input and output, parsing uploaded files, and integrating with systems that define a character encoding. A reliable production design decodes bytes when data enters the application, uses str while applying text rules, and encodes the final text when data leaves the application.
Why Interviewers Ask This
Interviewers ask this question to check whether a candidate understands the boundary between Unicode text and binary data in Python. They also evaluate whether the candidate can choose an explicit encoding, handle invalid input safely, reason about runtime errors, and prevent data corruption at production input and output boundaries.
Common interview mistakes
Common mistakes include treating bytes as if they were already text, using a different encoding for decoding than was used for encoding, relying on an environment dependent default encoding, and combining str and bytes without an explicit conversion. Joining str values with bytes, or writing the wrong type to a text or binary stream, raises TypeError. Comparing str and bytes does not decode either value and normally returns False for equality. Using ignore can silently remove invalid data. Repeated conversions also create unnecessary work and temporary objects.
Interview tip
Start with the main boundary rule. State that str represents Unicode text and bytes represents binary data. Then explain encode, decode, matching encodings, strict errors, allocation cost, and why explicit conversion prevents silent corruption.
Interviewer may ask next
What happens when bytes are decoded with the wrong encoding?
The operation may raise UnicodeDecodeError, or it may return incorrect text if the chosen encoding accepts that byte sequence. The exact behavior depends on the bytes, the selected encoding, and the error strategy. This matters because a successful decode does not prove that the correct encoding was used. Production code should obtain the encoding from the protocol, file format, metadata, or another trusted contract.
When should a production system use a nonstrict decoding error strategy?
A production system should use one only when its data policy clearly defines how malformed bytes must be handled. Replace can keep display or logging available by inserting replacement characters. Ignore removes invalid data and risks silent loss. Surrogateescape can preserve certain undecodable operating system bytes so they can be encoded back later. Strict remains the safest default when corrupted or unexpected input should stop processing.
6. How does functools.lru_cache work?Language SpecificMediumGoogle
functools.lru_cache saves a function result and returns it again when the function receives the same arguments. It builds a cache key from the arguments, so every argument must be hashable. When a limited cache becomes full, it removes the least recently used entry. I use it for repeated deterministic work, but I avoid it when results can change unless I have a clear way to clear or replace stale entries.
Use functools.lru_cache when repeated calls with the same hashable arguments should produce the same result. It performs memoization, which means it stores a completed result and reuses it later.
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 builds a cache key from positional and keyword arguments. Lists and dictionaries cannot be direct arguments because they are not hashable. Calls that are logically similar can still create separate entries when their argument forms differ, such as different keyword argument orders.
maxsize limits how many entries are kept. When the limit is reached, the least recently used entry is removed. maxsize=None disables eviction, so the cache can keep growing. maxsize=0 records misses but does not store results.
cache_info() reports hits, misses, maxsize, and current size. cache_clear() removes all entries and resets the statistics. The public interface does not provide a way to remove only one selected key.
The cache keeps references to arguments and return values until entries are removed. This can increase memory use. Its internal state remains consistent across threads, but the wrapped function can still run more than once for the same key during simultaneous misses. Use it for deterministic calculations and stable lookups, not important side effects or frequently changing external data.
Example
The function uses maxsize=2, so it keeps at most two results. The first keyboard call is a miss and runs the function body. The second keyboard call is a hit and returns the saved result. The mouse and monitor calls are misses. Adding monitor removes keyboard because keyboard is then the least recently used entry. The first cache_info() call reports one hit, three misses, a maximum size of two, and a current size of two. cache_clear() then removes all entries and resets the statistics to zero.
Code
from functools import lru_cache
# Keep at most two completed results.@lru_cache(maxsize=2)defload_price(product: str) -> float:
# This line runs only when the cache does not contain the key.print(f"Loading price for {product}")
# Use fixed data so each product always has the same result.
prices = {
"keyboard": 50.0,
"mouse": 25.0,
"monitor": 200.0,
}
return prices[product]
# This call is a cache miss.print(load_price("keyboard"))
# This call is a cache hit.print(load_price("keyboard"))
# These calls add two more keys.print(load_price("mouse"))
print(load_price("monitor"))
# This reports one hit, three misses, maxsize two, and current size two.print(load_price.cache_info())
# Remove all cached results and reset the statistics.
load_price.cache_clear()
print(load_price.cache_info())
Where it is used
It is useful for repeated calculations, recursive functions, parsing, configuration lookup, metadata lookup, and stable reference data. It works best when calls repeat often, the function is expensive, arguments are hashable, and results remain valid. It should be used carefully for database queries, network responses, current time, permissions, and other changing data because cached values can become stale.
Why Interviewers Ask This
Interviewers ask this question to check whether a candidate understands decorators, memoization, argument based cache keys, hashability, eviction, memory retention, invalidation, and concurrent calls. It also tests whether the candidate can judge when caching is safe and useful in production.
Common interview mistakes
Common mistakes include passing lists or dictionaries as arguments, caching functions with side effects, assuming cached data refreshes automatically, and using maxsize=None without considering memory growth. Another mistake is expecting cache_clear() to remove only one entry. Developers may also assume that thread safe cache bookkeeping means the wrapped function can never run twice for the same missing key.
Interview tip
Start by saying that lru_cache memoizes results by argument key. Then explain hashability, maxsize eviction, cache inspection, full cache clearing, stale data, memory retention, and duplicate work during simultaneous misses.
Interviewer may ask next
Can two threads compute the same missing cache key at the same time?
Yes. The cache remains internally consistent, but two threads can both miss the same key and run the wrapped function before either result is stored. This matters when the function is expensive or has side effects. The tradeoff is safe shared cache bookkeeping without a guarantee that each missing key is computed only once.
When should maxsize=None be avoided?
Avoid maxsize=None when the function can receive many different argument combinations or when arguments and results are large. This setting removes the eviction limit, so cached references can keep increasing memory use until cache_clear() is called, the wrapper is discarded, or the process ends. The tradeoff is keeping every result for a possible future hit while accepting unbounded cache growth.
7. How does Python's logging hierarchy propagate records?Language SpecificMediumGoogle
i Question Details
Explain logger names, levels, handlers, formatters, propagation, duplicate records, and production configuration.
Short Interview Answer (30-60 seconds)
Python loggers form a hierarchy from dot separated names. A logger such as app.service is a child of app and ultimately of the root logger. After the originating logger accepts a logging call, it creates one LogRecord, sends it to its own handlers, and then passes the same record to ancestor handlers while propagate is true. I usually place shared handlers on the root logger and avoid attaching equivalent handlers to child loggers because that often causes duplicate output.
Detailed Explanation
The practical rule is to configure shared handlers near the root logger and let module loggers propagate records upward. Logger names use dots to form a hierarchy. For example, app.service is a child of app, and app is below the root logger. A logging call first checks whether it is enabled by the originating logger's effective level. An effective level may come from that logger or from the nearest ancestor with a level other than NOTSET. If enabled, Python creates one LogRecord. The originating logger applies its filters, then offers the record to its handlers. Each handler checks its own level and filters before formatting and emitting the record. If propagate is true, Python passes the same record directly to handlers on each ancestor. Ancestor logger levels and ancestor logger filters are not checked during this step. A formatter belongs to a handler and converts the record into text or another output form. Duplicate lines usually appear when a child handler and an ancestor handler both emit the same propagated record. Setting propagate to false stops the ancestor walk. In production, configure logging once during application startup, use logging.getLogger(__name__) in modules, and avoid adding handlers repeatedly.
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?
Where it is used
This behavior is used in web services, background workers, command line tools, libraries, and applications with many modules. Each module can use logging.getLogger(__name__), while the application controls output centrally through root handlers. A selected subsystem can set propagate to false when it needs an isolated destination, such as a separate audit file, and has complete local handler configuration.
Why Interviewers Ask This
Interviewers ask this question to check whether a candidate understands the runtime path of a Python log record. They also evaluate whether the candidate can configure logger levels, handler levels, formatters, filters, and propagation without losing records or producing duplicate output.
Common interview mistakes
Common mistakes include attaching handlers to both a child logger and the root logger, which can emit the same record more than once. Another mistake is expecting an ancestor logger level to reject a propagated record. During propagation, Python calls ancestor handlers directly, so handler levels and handler filters control those records. Developers also sometimes add handlers every time a module is imported, attach a formatter to a logger instead of a handler, forget that propagate is true by default, or set propagate to false without giving the logger a handler that can emit the record.
Interview tip
Explain the runtime path in order. The originating logger checks its effective level, creates one record, applies its filters, calls its handlers, and then passes the same record to ancestor handlers while propagation remains enabled. Clearly mention that ancestor logger levels and filters are skipped, but ancestor handler levels and filters still apply.
Interviewer may ask next
Do ancestor logger levels filter a propagated record?
No. During propagation, Python passes the existing LogRecord directly to handlers attached to ancestor loggers. The ancestor logger levels and logger filters are not checked. Each ancestor handler still checks its own level and filters. This matters because raising the root logger level does not necessarily block a record already accepted by a child logger, while raising the root handler level can block that record.
What are the tradeoffs of setting propagate to false?
Setting propagate to false stops the record from reaching ancestor handlers after the current logger handles it. This is useful when a subsystem needs isolated output or must avoid duplicate emission. The tradeoff is that the subsystem no longer benefits from central root handling, so it needs complete local handler, level, formatter, filter, and destination configuration. It may also become harder to keep logging policy consistent across the application.
8. How does Python's pickle protocol serialize objects, and what are its security risks?Language SpecificMediumGoogle
i Question Details
Explain serialization hooks, protocol versions, object reconstruction, compatibility limits, and why untrusted pickle data must not be loaded.
Short Interview Answer (30-60 seconds)
The main rule is that I only unpickle data from a trusted source. Pickle writes instructions that describe how Python should rebuild an object graph, including object types, state, and shared references. Classes can customize this process with hooks such as __getstate__, __setstate__, and __reduce__. Unpickling is dangerous because reconstruction instructions can import objects and call functions, so malicious pickle data can execute arbitrary code.
The practical rule is simple. Never load pickle data from an untrusted source. Pickle does not store only plain values. It writes a stream of instructions that tells Python how to rebuild an object graph. The stream can describe object types, attribute state, containers, and shared references.
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 normal instance can save its state automatically. A class can customize saved state with __getstate__ and restore it with __setstate__. More advanced classes can use __reduce__ or __reduce_ex__ to provide a reconstruction callable, its arguments, and optional state. During loading, Python normally finds classes and functions by module and name. The object is commonly created without calling __init__.
Python defines protocol versions zero through five. A newer protocol may reduce data size or improve speed, but an older Python version may not support it. Protocol five can also place large binary buffers outside the main pickle stream.
Pickle is useful for trusted Python systems, but it is not a stable language neutral storage format. Renamed classes and changed definitions can break old data. Saving and loading take time and memory related to the object graph and serialized data. For untrusted input, use a safer data format such as JSON.
Example
The example serializes a trusted Session object with protocol five. The __getstate__ hook copies the instance dictionary and removes the temporary connection value, so the live resource is not stored. During loading, Python creates the object and passes the saved state to __setstate__. That hook restores the saved attributes and creates a new connection value. The result keeps the user name and roles while replacing the temporary resource. This trusted round trip demonstrates serialization hooks, but it does not make untrusted pickle data safe.
Code
import pickle
classSession:
def__init__(self, user_name, roles):
# Store normal application stateself.user_name = user_name
self.roles = roles
# Represent a temporary resource that should not be serializedself.connection = "active connection"def__getstate__(self):
# Copy the state so the original object is not changed
state = self.__dict__.copy()
# Remove the temporary resource from the saved state
state.pop("connection", None)
return state
def__setstate__(self, state):
# Restore the attributes that were storedself.__dict__.update(state)
# Create a new temporary resource after reconstructionself.connection = "new connection"# Create an object from trusted application data
original = Session("Maya", ["reader", "editor"])
# Serialize the trusted object with protocol five
payload = pickle.dumps(original, protocol=5)
# Load the payload only because this process created and controls it
restored = pickle.loads(payload)
print(restored.user_name)
print(restored.roles)
print(restored.connection)
Where it is used
Pickle is useful for trusted Python data such as temporary caches, internal test fixtures, saved application state, and communication between controlled Python processes. It should not be used for uploaded files, public API requests, browser input, or messages from systems that are not fully trusted. Long term records should usually use a documented and versioned data format instead.
Why Interviewers Ask This
Interviewers ask this question to test whether a candidate understands Python object serialization, object reconstruction, custom serialization hooks, protocol compatibility, and the serious production risk of loading pickle data from an untrusted source.
Common interview mistakes
Common mistakes include treating pickle as a plain data format, loading pickle data from a network or uploaded file, assuming validation after loading prevents code execution, and believing encryption alone makes unknown data safe. Other mistakes include expecting __init__ to run during reconstruction, moving or renaming a class without considering stored data, choosing a protocol that an older Python version cannot read, and trying to serialize live resources such as sockets, locks, open files, or database connections. A signature can detect unauthorized changes only when the signing key is protected, but it does not make data from an untrusted signer safe.
Interview tip
Start with the security rule. Then explain that pickle stores reconstruction instructions, describe the main hooks, mention protocol compatibility, and state clearly that unpickling malicious data can execute code.
Interviewer may ask next
What happens if a class is moved or changed after its instances were pickled?
Loading can fail or restore state that no longer matches the class. Pickle normally records a class by its module and name, so the class must remain importable from the expected location. Added, removed, or renamed attributes can also cause incorrect state. A version field and careful __setstate__ migration logic can support known changes, but this adds maintenance work and does not provide unlimited long term compatibility.
When should JSON be used instead of pickle?
Use JSON when data crosses a trust boundary, must be read by another language, or needs a stable and inspectable format. JSON stores a limited set of plain values and does not reconstruct arbitrary Python objects by calling their functions. This makes it safer for public input. The tradeoff is that custom classes, shared references, bytes, and other Python specific values must be converted to an explicit data representation.
9. How do Python's collections.Counter and defaultdict differ?Language SpecificMediumGoogle
i Question Details
Compare default behavior, missing keys, counting operations, arithmetic support, and appropriate use cases.
Short Interview Answer (30-60 seconds)
Counter is the better choice when the values represent counts. A missing key accessed with square brackets returns zero without adding that key. defaultdict is better when a missing key should create and store a value from a factory, such as a new list, set, or integer. Counter also provides counting methods and arithmetic between counters, while defaultdict mainly provides normal dictionary behavior with automatic value creation.
Use Counter for frequencies and defaultdict for automatic value creation. Counter is a dictionary subclass for counting hashable objects. It can count an iterable, add counts with update, subtract counts, return common items, calculate the total, and combine counters with arithmetic. Square bracket access for a missing key returns zero without inserting the key. Counter can still store zero or negative counts after assignment, update, or subtract.
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 defaultdict stores a default factory. When square bracket access requests a missing key, Python calls that factory with no arguments, inserts the returned value, and returns it. For example, defaultdict(list) creates a separate empty list for each new key. If the factory is None, missing square bracket access raises KeyError.
The get method does not trigger either special missing key behavior. It returns the supplied default, or None. Both types use normal dictionary storage for their entries. A defaultdict can allocate memory and grow during a missing square bracket read. Counter arithmetic scans stored keys and normally removes results whose counts are zero or negative. ([docs.python.org](https://docs.python.org/3/library/collections.html?utm_source=chatgpt.com))
Example
The example counts letters with Counter and groups names with defaultdict. Reading a missing Counter key with square brackets returns zero and leaves the mapping unchanged. Reading a missing defaultdict key with square brackets calls list, stores a new empty list, and returns that list. The example also shows that get does not call the default factory. Counter addition combines matching counts and keeps only positive results, while defaultdict has no specialized counting arithmetic.
Code
from collections import Counter, defaultdict
# Counter is designed for counting hashable values.
letter_counts = Counter("banana")
print(letter_counts)
# A missing Counter key returns zero.# Reading it does not insert the key.print(letter_counts["z"])
print("z"in letter_counts)
# Counter provides operations designed for counts.
more_counts = Counter("band")
combined_counts = letter_counts + more_counts
print(combined_counts)
print(combined_counts.most_common(2))
# defaultdict creates and stores a value for a missing key.
names_by_team = defaultdict(list)
names_by_team["blue"].append("Asha")
names_by_team["blue"].append("Ben")
print(dict(names_by_team))
# Square bracket access calls list and inserts the missing key.print(names_by_team["red"])
print("red"in names_by_team)
# get does not call the default factory or insert the key.print(names_by_team.get("green"))
print("green"in names_by_team)
Where it is used
Counter is used for word frequencies, event totals, inventory quantities, vote totals, request categories, and finding common values. defaultdict is used for grouping records, building adjacency lists, collecting errors by category, creating indexes, and storing sets or lists for keys that appear for the first time. In production code, Counter makes counting intent clear. defaultdict removes repeated missing key checks, but developers should remember that square bracket reads can create entries and increase memory use.
Why Interviewers Ask This
Interviewers ask this question to check whether a candidate understands specialized mapping types in the Python Standard Library. They want to see whether the candidate knows how missing keys behave, whether reading a key changes the mapping, which counting operations Counter provides, and when automatic value creation is useful. The question also tests whether the candidate can choose a clear data structure and avoid hidden changes to stored data.
Common interview mistakes
A common mistake is assuming both types insert a value when a missing key is read. Counter returns zero without inserting the key, while defaultdict inserts a factory result during square bracket access. Another mistake is expecting get to call the defaultdict factory or return zero for a missing Counter key. It does neither unless an explicit default is supplied. Developers may also use defaultdict(int) for simple counting when Counter would express the purpose more clearly. Counter arithmetic is sometimes misunderstood because its result normally omits counts of zero or less, even though a Counter can store such counts. Another mistake is forgetting that zero and negative Counter entries remain stored until they are deleted or removed by an operation such as unary plus. A factory that returns the same shared mutable object can also make several defaultdict keys refer to one object.
Interview tip
Start with the decision rule: use Counter to count and defaultdict to create missing values. Then explain whether square bracket access inserts a missing key, mention get behavior, and give one small counting example and one grouping example.
Interviewer may ask next
Does accessing a missing key always modify Counter or defaultdict?
No. Counter square bracket access returns zero for a missing key without inserting it. defaultdict square bracket access calls its default factory, inserts the returned value, and then returns it. If the defaultdict factory is None, the access raises KeyError. The get method does not trigger these missing key rules. This matters because a read can allocate a value and increase the size of a defaultdict, but the same Counter read does not. ([docs.python.org](https://docs.python.org/3/library/collections.html?utm_source=chatgpt.com))
When should you choose defaultdict(int) instead of Counter?
Choose defaultdict(int) when automatic zero creation is part of broader dictionary logic and specialized counting operations are not needed. Choose Counter when the values represent counts and you need methods such as most_common, total, update, subtract, or arithmetic between counters. Counter usually communicates counting intent more clearly. defaultdict is more flexible for custom value creation, but missing square bracket access inserts entries and can increase memory use.
10. How do weak references work in Python?Language SpecificMediumGoogle
i Question Details
Explain weakref references, callbacks, supported object types, weak containers, and use cases for caches without extending object lifetime.
Short Interview Answer (30-60 seconds)
A weak reference lets me access an object without keeping it alive. I create one with weakref.ref and call it to get the object. It returns the object while the object is alive, then returns None after the object is collected. This is useful for caches and registries that should not control object lifetime.
Detailed Explanation
Use a weak reference when code needs to observe an object but must not keep it alive. The weakref.ref function creates a reference object. Calling it returns the target while that target still exists. When no strong references remain, the target becomes eligible for collection. After collection, the weak reference returns None.
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?
In CPython, many objects are destroyed as soon as their reference count reaches zero. Objects in reference cycles may remain until cyclic garbage collection runs. Other Python implementations may collect objects at different times, so production code must not depend on exact timing.
A callback may be passed to weakref.ref. Python calls it when the target is finalized. The callback receives the weak reference, not the destroyed target.
Most user defined class instances support weak references. Some built in types, including list and dict, do not support them directly, although subclasses can. Classes that define slots must include __weakref__ to support them.
WeakKeyDictionary, WeakValueDictionary, WeakSet, and WeakMethod provide common weak reference patterns. They are useful for caches, metadata, registries, and bound method callbacks. They add object and lookup overhead, and entries may disappear whenever their targets are collected.
Where it is used
Weak references are used in memory sensitive caches, object registries, metadata mappings, observer systems, plugin tracking, and callback systems. WeakValueDictionary is useful when cached values should disappear after the rest of the program stops using them. WeakKeyDictionary can attach information to objects without extending their lifetime. WeakMethod is useful for bound method callbacks because an ordinary bound method object may be temporary.
Why Interviewers Ask This
Interviewers ask this question to test whether a candidate understands strong references, object lifetime, garbage collection, supported object types, and memory aware design in Python. It also shows whether the candidate can use weak references safely in caches, registries, and callback systems.
Common interview mistakes
Common mistakes include expecting a weak reference to keep its target alive, using its result without checking for None, and assuming every Python object supports weak references. Another mistake is forgetting __weakref__ in a class that defines slots. Developers may also rely on callback timing, treat a weak cache as permanent storage, or use weakref.ref for a bound method instead of WeakMethod. A callback must not expect access to the destroyed target because it receives only the weak reference.
Interview tip
Start by contrasting strong and weak references. State that a weak reference does not extend object lifetime and returns None after collection. Then mention callbacks, supported types, weak containers, collection timing, and the cache retention tradeoff.
Interviewer may ask next
What happens when a class uses slots without __weakref__?
Creating a weak reference to its instances raises TypeError. The exact change needed is to include __weakref__ in the slots declaration. This matters because defining slots removes the normal weak reference support unless that special slot is present. Adding it enables weak references but uses a small amount of additional storage per instance.
When should a cache use WeakValueDictionary instead of dict?
Use WeakValueDictionary when the cache must not be the reason a value stays alive. Its exact behavior is to hold values weakly and remove entries after those values are collected. This can reduce unwanted memory retention, but a cached value may disappear between uses, causing a cache miss and repeated creation. Use a normal dict when retention must be predictable.
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.
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.