Python Full-Stack Interview Questions 21–25 (Duck Typing, Identity, Memory, with, Import)
This lesson answers five practical Python interview questions with simple, beginner-friendly explanations, real-world analogies, and runnable code examples that include expected output. Read slowly, try each example in your editor, and use the short lists to anchor the main ideas.
21. Explain duck typing in Python.
Duck typing is a style of dynamic typing where the suitability of an object is determined by the presence of methods and attributes, not by its explicit type. The classic phrase is: "If it walks like a duck and quacks like a duck, it's a duck." In practice, this means you write code that operates on objects that implement the expected behavior, rather than checking the object's class.
- Focus on the behavior (methods/attributes) an object provides.
- Avoid strict `isinstance()` checks unless necessary; prefer duck-typed interfaces.
- Duck typing encourages flexible, easier-to-test code, but careful error handling is important.
Example: a function that expects something that can be iterated and printed. It doesn't care about the concrete type.
def print_all_things(things):
"""
We only assume 'things' is iterable and its items are printable.
"""
for item in things:
print(item)
# Works for a list
print_all_things([1, 2, 3])
# Works for a generator
print_all_things(x * x for x in range(3))
# Works for a custom container that implements __iter__
class Bag:
def __init__(self, items):
self._items = list(items)
def __iter__(self):
return iter(self._items)
print_all_things(Bag(["apple", "banana"]))
Expected output when run:
# Output:
1
2
3
0
1
4
apple
banana 22. What is the difference between `is` and `==`?
`==` checks for value equality — it asks "are these two objects equal in value?" `is` checks for identity — it asks "are these two names pointing to the exact same object in memory?" In everyday terms: `==` compares the contents written on two index cards; `is` checks whether both index cards are the very same physical card.
- `==` calls the object's `__eq__` method (or falls back to identity if not implemented).
- `is` checks whether the two operands share the same object id (`id(a) == id(b)`).
- Use `==` for value comparisons and `is` for checking `None` or singletons.
Examples showing both behaviors:
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True, same contents
print(a is b) # False, different objects
print(a is c) # True, same object
x = None
print(x is None) # True — use 'is' for None checks Expected output:
# Output:
True
False
True
True 23. How is memory allocated to variables in Python?
Python uses an object model with automatic memory management. Every value in Python is an object; variables are simply names (references) that point to objects. Memory allocation and reclaiming are handled by the Python runtime (the interpreter), primarily via reference counting plus a cyclic garbage collector to clean cycles.
- When you create a value (e.g., `x = 1000`), the interpreter allocates memory for a Python object representing that value.
- Names (variables) hold references to objects; multiple names can reference the same object.
- Reference counting: when no references remain to an object, it's deallocated. Cyclic GC handles reference cycles (objects referencing each other).
Simple demonstration showing identities and shared references:
a = [1, 2, 3]
b = a # b references the same list object
c = a.copy() # c is a new list with the same contents
print(id(a)) # object id of a
print(id(b)) # same as a
print(id(c)) # different id
# Changing through one name affects the shared object:
b.append(4)
print(a) # [1, 2, 3, 4] Expected output (ids will differ on your machine; shown here as placeholders):
# Output (example):
140319123456320
140319123456320
140319123457088 [1, 2, 3, 4] Notes and interview tips:
- Be ready to explain reference vs copy (shallow vs deep) and when to use each.
- Understand that small integers and short strings may be interned (implementation detail); avoid relying on that behavior.
- Mention `gc` module for manual inspection and `sys.getrefcount()` for reference counts (remember `getrefcount` adds a temporary reference when called).
24. What is Python's `with` statement?
The `with` statement simplifies resource management by ensuring setup and teardown code runs reliably. It's commonly used for files, locks, and network connections. Objects used in a `with` block implement the context manager protocol — they provide `__enter__` (setup) and `__exit__` (cleanup) methods.
- `__enter__` runs when entering the block and can return a helpful value.
- `__exit__` runs when exiting the block, even if an exception occurs; it can suppress exceptions when needed.
- Use `with` to avoid forgetting cleanup (e.g., `file.close()`), making code safer and cleaner.
File-handling example and a simple custom context manager:
# Using with for file handling
with open("example.txt", "w") as f:
f.write("hello")
# File is closed automatically here
# Custom context manager
class Timer:
import time
def enter(self):
self.start = self.time.time()
return self
def exit(self, exc_type, exc, tb):
self.end = self.time.time()
print(f"Elapsed: {self.end - self.start:.4f}s")
with Timer():
total = sum(range(1000000)) # do some work Example output (timing will vary):
# Output (example):
Elapsed: 0.0357s 25. Explain Python's import system.
Python's import system locates and loads modules so you can reuse code. When you write `import module`, Python follows a defined process: it searches for the module in a set of locations (the module search path), loads and executes the module file once, and caches the loaded module in `sys.modules` so subsequent imports are fast and consistent.
- Search order: built-in modules → `sys.modules` cache → file system locations listed in `sys.path` (including the current directory and site-packages).
- Import forms: `import module`, `from module import name`, and `import package.module as alias` — each has different namespace effects.
- Packages are directories with a `__init__.py` (modern Python can have namespace packages without `__init__.py`, but the `__init__` file is often used for package initialization).
- `sys.modules` prevents repeated execution of a module; modifying the module object in `sys.modules` affects later imports.
Small example demonstrating import behavior and `sys.modules` caching:
# Create a module file named mymod.py with:
# print("mymod loaded")
# value = 42
# Then run this script:
import importlib, sys
import mymod # prints "mymod loaded"
import mymod # no print, loaded from sys.modules
importlib.reload(mymod) # forces re-execution and prints again
print("mymod.value =", mymod.value) Expected terminal sequence:
# Output:
mymod loaded
mymod loaded
mymod.value = 42 Interview tips for imports:
- Know how `sys.path` is constructed and why relative imports can behave differently in packages vs scripts.
- Explain how circular imports occur and strategies to avoid them (defer imports, refactor, or use local imports inside functions).
- Mention `importlib` for dynamic imports and `__all__` to control `from module import *` behavior.
Closing notes: These five topics often come up in interviews because they reveal both conceptual understanding and practical habits. Practice writing small programs that demonstrate each idea, and when answering in interviews, use short analogies (like "names point to objects" or "duck typing: if it quacks...") to make your explanation friendly and memorable.