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: sys.modules cache → built-in modules → 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.

Check your understanding

3 questions · no sign-up · misses join your review queue, on this device only

0/3 answered
Question 1

1.When is is the correct operator to use?

Question 2

2.What guarantees does the with statement give you?

Question 3

3.What is a circular import, and what usually fixes it?

0 / 74 ranges

Progress is stored in this browser only - no sign-up, nothing sent anywhere.