InterviewQAs

Python Constructors

Download as PDF
All questions in this page are included
Preparing…
Download PDF
PC
Python Constructors

Python constructors are frequently used to establish object state, validate incoming data, initialize dependencies, and enforce business rules at creation time. In production systems, constructor design often influences maintainability more than many developers realize.

A well-designed constructor creates objects that are immediately usable and difficult to misuse. Instead of requiring multiple setup methods after instantiation, constructors can ensure that required attributes, configurations, and dependencies are available from the beginning.

Real-world applications often involve constructors that perform input validation, normalize data formats, initialize caches, configure external service clients, or prepare internal structures needed by the object. These responsibilities should remain focused on initialization rather than business processing.

Constructor implementation becomes particularly important when building reusable libraries, data processing pipelines, API clients, and enterprise applications. Decisions around optional parameters, default values, inheritance, and object immutability can significantly affect code quality.

Understanding constructor behavior helps developers create predictable object lifecycles, avoid hidden side effects, and build classes that remain easy to test, extend, and maintain as applications grow.

Question 01

Why is performing validation inside a constructor often preferable to validating data later in application execution?

EASY

Validation inside a constructor prevents invalid objects from entering the system. If an object is created with incorrect state and validation happens later, bugs can spread across multiple components before the issue is detected. Catching problems during object creation makes failures immediate and easier to diagnose.

Consider a payment processing class that requires a positive transaction amount. If the constructor validates the amount, every instance of that class is guaranteed to start in a valid state. Other methods can then focus on business logic instead of repeatedly checking the same condition.

There is a balance to maintain. Constructors should validate essential requirements but avoid heavy operations such as database queries or network calls unless absolutely necessary. The goal is to establish a valid object, not perform its primary business function.

Question 02

Which statements about the __init__ constructor are correct?

MEDIUM
  • A __init__ is called automatically after an instance is created.
  • B __init__ must explicitly return the created object.
  • C Multiple constructor parameters can be used to initialize object state.
  • D An object cannot exist unless __init__ is defined.

__init__ is an initialization method that executes automatically after object creation. It is commonly used to assign attributes, validate inputs, and prepare the object for use.

The method should not return the instance. Python creates the object before calling __init__. Classes can also exist without defining __init__, in which case Python uses inherited behavior from parent classes or the default object implementation.

Question 03

Create a constructor that validates an employee ID and normalizes employee names during object creation.

EASY

The constructor ensures that every Employee object follows a consistent identifier format and stores names in a normalized form. This prevents repeated cleanup logic throughout the application.

In enterprise systems, constructors are often used for data normalization because it guarantees consistency at the moment an object enters the system.

# Python
class Employee:
    def __init__(self, employee_id, name):
        if not employee_id.startswith("EMP-"):
            raise ValueError("Employee ID must start with EMP-")

        self.employee_id = employee_id
        self.name = name.strip().title()

employee = Employee("EMP-1001", "  vijay bhaskar  ")
print(employee.employee_id)
print(employee.name)
Question 04

How should constructors be designed when a class depends on external services such as APIs or databases?

MEDIUM

A common practice is dependency injection, where required services are passed into the constructor rather than created internally. This makes the class easier to test and reduces coupling between components.

For example, an order processing class might receive a database repository object through its constructor. During testing, a mock repository can be supplied without modifying production code.

Creating database connections, API clients, or network sessions directly inside constructors often makes testing difficult and can introduce hidden side effects. Constructors should generally receive dependencies instead of constructing them.

Question 05

Which scenarios represent good constructor design practices?

HARD
  • A Initializing required attributes needed by the object
  • B Performing lengthy report generation every time an object is created
  • C Receiving dependencies through constructor parameters
  • D Keeping initialization logic focused on object setup

Constructors should focus on preparing an object for use. Assigning attributes, validating input, and accepting dependencies are common and beneficial responsibilities.

Heavy operations such as report generation, large file processing, or expensive network workflows can make object creation slow and unpredictable. Such operations are usually better placed in dedicated methods.

Question 06

Demonstrate constructor inheritance where a child class extends initialization from a parent class.

MEDIUM

The child constructor uses super() to reuse initialization logic from the parent class. This avoids duplicating code and ensures consistent setup behavior.

In large applications, inheritance hierarchies often rely on constructor chaining to initialize shared attributes while allowing specialized classes to add their own configuration.

# Python
class User:
    def __init__(self, username):
        self.username = username

class Admin(User):
    def __init__(self, username, permissions):
        super().__init__(username)
        self.permissions = permissions

admin = Admin("vijay", ["create", "delete"])
print(admin.username)
print(admin.permissions)
Question 07

Which statement correctly describes constructor return behavior in Python?

MEDIUM
  • A __init__ must return self explicitly.
  • B __init__ should return a dictionary of initialized attributes.
  • C __init__ normally returns None and initializes the existing instance.
  • D __init__ creates the object before Python allocates memory.

Python allocates and creates the object before __init__ executes. The purpose of __init__ is to configure that object, not create or return it.

Attempting to return another value from __init__ raises a TypeError because Python expects initialization behavior rather than object creation logic.

Question 08

Implement a constructor that uses keyword-only arguments to reduce incorrect parameter ordering during object creation.

HARD

Keyword-only parameters force callers to specify argument names. This prevents subtle bugs caused by incorrect positional argument ordering.

Configuration objects frequently benefit from this pattern because developers can immediately understand which value belongs to which parameter without reading implementation details.

# Python
class DatabaseConfig:
    def __init__(self, *, host, port, database):
        self.host = host
        self.port = port
        self.database = database

config = DatabaseConfig(
    host="localhost",
    port=5432,
    database="sales"
)

print(config.host)
print(config.port)
print(config.database)
Question 09

What risks arise when constructors contain excessive business logic, and how can those risks be reduced?

HARD

Constructors that perform significant business processing can become difficult to understand, test, and maintain. Creating an object may unexpectedly trigger database writes, API requests, file operations, or complex calculations that developers do not anticipate.

This hidden behavior can lead to performance problems because simply instantiating a class becomes expensive. It also complicates unit testing because object creation may require infrastructure dependencies or extensive setup.

A common solution is to keep constructors focused on initialization and move business workflows into dedicated methods or service layers. Objects should generally be lightweight to create and explicit about when expensive operations occur.

Question 10

Create a constructor that uses default values while allowing callers to override configuration settings.

MEDIUM

Default constructor values provide sensible behavior for common use cases while allowing customization when requirements differ.

This pattern is widely used in SDKs, API clients, messaging systems, and integration frameworks where most consumers use defaults but advanced users require additional control.

# Python
class RetryPolicy:
    def __init__(self, max_retries=3, timeout_seconds=30):
        self.max_retries = max_retries
        self.timeout_seconds = timeout_seconds

standard_policy = RetryPolicy()
custom_policy = RetryPolicy(max_retries=5, timeout_seconds=60)

print(standard_policy.max_retries)
print(custom_policy.max_retries)
Question 11

Explain how default arguments in constructors can simplify object creation in Python.

EASY

Default arguments allow constructors to provide fallback values when certain parameters are not supplied. This reduces the need for multiple overloaded constructors and keeps object creation concise.

For example, a network client class might have default timeout and retry parameters, allowing most users to rely on safe defaults while still giving advanced users the ability to override them.

By using defaults thoughtfully, constructors can balance usability and flexibility without requiring repetitive initialization logic throughout the codebase.

Question 12

Which statements about constructor overloading in Python are correct?

MEDIUM
  • A Python supports multiple __init__ methods with different signatures.
  • B Overloading can be simulated using default arguments.
  • C Conditional logic inside __init__ can handle varying argument types.
  • D Multiple constructors can be defined using @overload decorator for runtime selection.

Python does not natively support multiple __init__ methods like Java or C++. Instead, developers use default arguments or type-checking logic inside a single __init__ method to achieve similar flexibility.

The @overload decorator exists for type hinting and static type checkers, but it does not create runtime multiple constructor behavior.

Question 13

Write a constructor that raises an exception if a required configuration key is missing in a dictionary.

MEDIUM

This constructor enforces mandatory configuration keys, preventing misconfigured objects from being created. It is particularly useful when initializing database connections or API clients.

By validating input at construction time, developers can detect errors early and ensure that objects always start in a usable state.

# Python
class ConfigLoader:
    def __init__(self, config):
        required_keys = ['host', 'port']
        missing = [key for key in required_keys if key not in config]
        if missing:
            raise KeyError(f"Missing configuration keys: {missing}")
        self.host = config['host']
        self.port = config['port']

config = {'host': 'localhost', 'port': 8080}
loader = ConfigLoader(config)
print(loader.host, loader.port)
Question 14

Describe the difference between __new__ and __init__ and when you might need to override __new__.

MEDIUM

__new__ is responsible for creating and returning a new instance, while __init__ initializes the instance after it has been created. Normally, only __init__ is overridden.

Overriding __new__ is necessary when you need to control instance creation itself, such as implementing singletons, immutable objects, or custom metaclass behavior.

In typical applications, overriding __new__ is rare. Most initialization and validation work is safely handled in __init__ without touching the instance creation mechanics.

Question 15

Which of the following are best practices for constructors in Python?

HARD
  • A Keep initialization logic simple and predictable
  • B Avoid performing expensive operations like network calls
  • C Perform all business logic during object creation
  • D Use dependency injection for external resources

Constructors should focus on setting up the object state and dependencies, not performing heavy business operations.

Expensive or side-effecting tasks should be executed in separate methods to keep object instantiation fast and safe.

Question 16

Implement a constructor that automatically converts all string attributes of a class to lowercase.

MEDIUM

The constructor dynamically assigns attributes while normalizing string values to lowercase. This ensures consistency without requiring repeated calls to lower() throughout the code.

Such constructors are useful in data ingestion, logging, and user input normalization scenarios.

# Python
class TextRecord:
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            if isinstance(value, str):
                setattr(self, key, value.lower())
            else:
                setattr(self, key, value)

record = TextRecord(name="Vijay", city="DELHI")
print(record.name)
print(record.city)
Question 17

Explain the tradeoffs of using complex logic inside constructors versus separate initialization methods.

HARD

Complex logic inside constructors can make objects difficult to instantiate, test, and understand. Hidden side effects or dependencies can lead to performance issues or unexpected errors.

Separating initialization into dedicated methods allows for explicit control over execution, lazy initialization, and better testability. Developers can instantiate objects quickly without triggering heavy processing.

The tradeoff is convenience versus control. Constructors provide immediate usability, but moving logic out increases transparency and flexibility in larger, production-grade applications.

Question 18

Which of these techniques help improve constructor testability?

MEDIUM
  • A Dependency injection of external services
  • B Using default arguments for optional parameters
  • C Hardcoding file paths or API URLs inside __init__
  • D Splitting heavy processing into separate methods

Testable constructors avoid creating external dependencies internally. By injecting mocks or using defaults, tests can run without network or file system access.

Heavy processing should be separated to allow unit tests to instantiate objects quickly and verify their initial state without triggering side effects.

Question 19

Write a constructor for a singleton class that ensures only one instance is ever created.

HARD

By overriding __new__, the class ensures that only one instance exists. Subsequent calls return the same object, making it a true singleton.

This pattern is used for global configuration objects, logging classes, and resources that must not have multiple instances in memory.

# Python
class Singleton:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super(Singleton, cls).__new__(cls)
        return cls._instance

    def __init__(self, value):
        self.value = value

a = Singleton(10)
b = Singleton(20)
print(a.value, b.value)
print(a is b)
Question 20

Demonstrate a constructor that initializes a class with both positional and keyword arguments safely.

MEDIUM

The constructor uses a required positional parameter for endpoint and keyword arguments for method, headers, and params. This prevents parameter misordering and provides flexibility.

Such patterns are common in SDKs and API wrappers where the endpoint is mandatory but configuration options vary by user needs.

# Python
class ApiRequest:
    def __init__(self, endpoint, method='GET', **kwargs):
        self.endpoint = endpoint
        self.method = method.upper()
        self.params = kwargs.get('params', {})
        self.headers = kwargs.get('headers', {})

request = ApiRequest('https://api.example.com', params={'id': 1})
print(request.endpoint)
print(request.method)
print(request.params)
print(request.headers)
Question 21

What are the practical benefits of using __init__ over manually setting attributes after object creation?

EASY

Using __init__ ensures that objects are always in a valid state immediately after creation. This reduces the risk of using partially initialized objects, which could lead to runtime errors.

It centralizes the initialization logic, making code cleaner and easier to maintain. Developers can rely on the object having all required attributes without having to remember to set them manually after creation.

Additionally, __init__ can perform validation, normalization, or dependency injection, which cannot be guaranteed if attributes are set manually later.

Question 22

Which techniques can help handle multiple initialization scenarios in a single constructor?

MEDIUM
  • A Using default arguments for optional parameters
  • B Checking argument types and values inside __init__
  • C Defining multiple __init__ methods with different signatures
  • D Using variable positional (*args) and keyword (**kwargs) arguments

Python does not support multiple __init__ methods with different signatures; overloading must be simulated using defaults, type checks, or *args/**kwargs.

These patterns allow a single constructor to handle flexible initialization while ensuring that the object remains consistent and valid.

Question 23

Create a constructor that initializes a list attribute and prevents shared references between instances.

MEDIUM

Using a copy of the input list ensures that each instance has its own independent task list, preventing unintended sharing between objects.

This approach is important in real-world scenarios where mutable objects passed to constructors could otherwise lead to subtle bugs if shared across instances.

# Python
class TaskManager:
    def __init__(self, tasks=None):
        self.tasks = tasks.copy() if tasks else []

manager1 = TaskManager(['task1', 'task2'])
manager2 = TaskManager()
manager1.tasks.append('task3')
print(manager1.tasks)  # ['task1', 'task2', 'task3']
print(manager2.tasks)  # []
Question 24

How can constructors improve code readability and maintainability in large projects?

MEDIUM

Constructors consolidate initialization logic in one place, reducing code duplication and making it immediately clear what state an object requires.

They allow developers to understand an object's required inputs and default behavior without having to inspect multiple setup functions or external assignment code.

In larger projects, constructors also help enforce consistent practices for validation, dependency injection, and configuration management across different modules and teams.

Question 25

Which approaches are suitable for constructors in classes requiring optional and mandatory configuration values?

HARD
  • A Use positional arguments for mandatory values and keyword arguments for optional ones
  • B Use only positional arguments for all parameters to simplify code
  • C Implement default values for optional parameters
  • D Require a configuration dictionary containing all values

Combining positional and keyword arguments provides clarity for mandatory and optional values.

Using defaults or a configuration dictionary provides flexibility, ensuring that objects can be easily extended or modified without breaking existing code.

Question 26

Demonstrate a constructor that converts date strings to datetime objects upon initialization.

MEDIUM

The constructor transforms input strings into datetime objects, ensuring consistent and type-safe internal representation.

This prevents repetitive parsing logic elsewhere and allows the object to provide immediate usability for date operations like comparison or formatting.

# Python
from datetime import datetime

class Event:
    def __init__(self, name, date_str):
        self.name = name
        self.date = datetime.strptime(date_str, '%Y-%m-%d')

meeting = Event('Project Kickoff', '2026-06-12')
print(meeting.name)
print(meeting.date)
Question 27

What precautions should be taken when constructors interact with mutable default arguments?

HARD

Mutable default arguments, such as lists or dictionaries, can lead to shared state across instances if used directly in a constructor. Changes in one instance may unintentionally affect others.

The recommended practice is to use None as the default and initialize a new object inside the constructor if the argument is not provided.

This ensures that each instance maintains its own independent mutable state, reducing bugs and improving code reliability in production systems.

Question 28

Which statements are true about using super() in constructors?

MEDIUM
  • A super() allows a child class to call the parent class __init__ method
  • B super() can be used to avoid duplicating initialization code
  • C super() automatically passes all arguments to the parent class without manual specification
  • D super() is only necessary when the parent class is abstract

Using super() ensures proper initialization of inherited attributes and prevents code duplication in subclass constructors.

Arguments must still be explicitly passed when the parent __init__ requires them; super() does not automatically forward all arguments.

Question 29

Implement a constructor that initializes nested objects for a parent-child relationship.

HARD

The Person constructor initializes an Address object internally, demonstrating nested object creation.

This pattern is common in modeling real-world relationships where objects naturally contain other objects as attributes.

# Python
class Address:
    def __init__(self, street, city):
        self.street = street
        self.city = city

class Person:
    def __init__(self, name, street, city):
        self.name = name
        self.address = Address(street, city)

person = Person('Vijay', 'MG Road', 'Bangalore')
print(person.name)
print(person.address.street)
print(person.address.city)
Question 30

Write a constructor that tracks the number of instances created for a class.

MEDIUM

The constructor increments a class-level variable each time a new instance is created.

This pattern is useful for monitoring object usage, debugging, or limiting the number of instances in scenarios like resource pools or singleton patterns.

# Python
class Tracker:
    instance_count = 0

    def __init__(self):
        Tracker.instance_count += 1

obj1 = Tracker()
obj2 = Tracker()
obj3 = Tracker()
print(Tracker.instance_count)