PracHub
QuestionsPremiumLearningGuidesInterview PrepCoaches
|Home/Coding & Algorithms/Chalk

Implement Python feature and resolver metaprogramming

Last updated: Apr 29, 2026

Quick Overview

This question evaluates proficiency in Python metaprogramming, specifically use of decorators, runtime type introspection, handling of type-annotated classes and forward references, and design of feature/resolver abstractions.

  • hard
  • Chalk
  • Coding & Algorithms
  • Software Engineer

Implement Python feature and resolver metaprogramming

Company: Chalk

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: hard

Interview Round: Technical Screen

You are given a skeleton for a small Python "feature" framework. The goal is to use metaprogramming to: 1. Parse type-annotated classes into `Feature` objects via a `@features` decorator. 2. Parse functions into `Resolver` objects via a `@resolver` decorator. 3. Implement an `execute` function that, given some known input feature values and desired output features, automatically computes the outputs by chaining resolvers. You must implement the parts marked `Implement me!` so that all the tests at the bottom pass. You may find the following tools useful (but they are not required): - `cls.__annotations__` - `cls.__name__` - `setattr(obj, key, value)` - `inspect.signature(fn)` - `typing.get_type_hints` --- ### Step 1: Parse features from annotated classes Implement the `@features` class decorator so that: - For each type-annotated attribute in the decorated class, you create a `Feature` instance with: - `name` equal to `"<ClassName>.<field_name>"` (for example, `"User.name"`). - `typ` equal to the Python type of that attribute (resolving forward references like the string annotation `"User"` to the actual class `User`). - The decorated class has a class attribute `features` which is a list of all `Feature` instances for that class in declaration order. - Each annotated attribute of the class (e.g., `User.id`, `User.email`) evaluates to a `Feature` instance with the corresponding `name` and `typ`, so that expressions like `User.id == Feature(name="User.id", typ=int)` are true. Skeleton and tests: ```python import dataclasses @dataclasses.dataclass(unsafe_hash=True) class Feature: name: str # e.g. "User.name" typ: type # e.g. str def features(cls): """Class decorator. Implement me! After decoration: - <Class>.features is a list[Feature] - <Class>.<field_name> is a Feature instance """ return cls @features class Card: id: int number: str owner: "User" # forward reference @features class User: id: int email: str name: str card_id: int is_fraud: bool def test_scalars(): assert User.features == [ Feature(name="User.id", typ=int), Feature(name="User.email", typ=str), Feature(name="User.name", typ=str), Feature(name="User.card_id", typ=int), Feature(name="User.is_fraud", typ=bool), ], User.features def test_forward_reference(): assert Card.features == [ Feature(name="Card.id", typ=int), Feature(name="Card.number", typ=str), Feature(name="Card.owner", typ=User), # Note: typ should be the class User, not the string "User" ], Card.features def test_properties(): assert User.id == Feature(name="User.id", typ=int) test_scalars() test_forward_reference() test_properties() ``` --- ### Step 2: Parse resolvers from functions Resolvers are functions that compute the value of one `Feature` from other `Feature`s. You must implement the `@resolver` decorator so that it: - Wraps the original function in a `Resolver` dataclass instance. - Extracts the input `Feature` objects from the function's parameter annotations in order. - Extracts the output `Feature` from the function's return annotation. - Stores the original function in `Resolver.fn` so that it can be called later. Assume resolver functions are annotated using `Feature` objects like `User.id`, `User.name` etc., for both parameters and return type. Skeleton and tests: ```python import inspect import dataclasses from typing import Callable, Any, TypeVar, ParamSpec, Generic P = ParamSpec("P") T = TypeVar("T") @dataclasses.dataclass class Resolver(Generic[P, T]): inputs: list[Feature] output: Feature fn: Callable[P, T] def resolver(fn: Callable[P, T]) -> Resolver[P, T]: """Function decorator. Implement me! Should return a Resolver instance whose: - inputs: parameter annotations (Feature objects) in order. - output: return annotation (a Feature object). - fn: the original function. """ return fn @resolver def get_user_name(id: User.id) -> User.name: if id == 1: return "elliot" return "joe" @resolver def get_user_email(id: User.id) -> User.email: if id == 1: return "elliot@chalk.ai" return "fraudster@chalk.ai" @resolver def get_user_fraud_score(name: User.name, email: User.email) -> User.is_fraud: return name.lower() not in email.lower() def test_resolvers(): assert get_user_name.output == User.name, get_user_name.output assert get_user_name.inputs == [User.id], get_user_name.inputs assert get_user_email.output == User.email, get_user_email.output assert get_user_email.inputs == [User.id], get_user_email.inputs assert get_user_fraud_score.output == User.is_fraud assert get_user_fraud_score.inputs == [User.name, User.email] test_resolvers() ``` You may additionally choose to register all created `Resolver` instances in some global registry so that `execute` (in Step 3) can find them, but the exact registration mechanism is up to you as long as the behavior matches the tests. --- ### Step 3: Implement the execution engine Implement the `execute` function that computes desired output features from a set of known inputs by chaining together resolvers defined using `@resolver`. Requirements: - `inputs` is a dictionary mapping either: - `Feature` objects to their concrete values (e.g., `{User.id: 1}`), or - optionally, any alternative keys you choose to support, as long as the provided tests pass. - `outputs` is a list of requested outputs, each being either a `Feature` or something you map to a `Feature`. - `execute` returns a dictionary `{Feature: value}` for all requested output features. - It must: - Use the registered `Resolver` objects created by the `@resolver` decorator. - Repeatedly apply resolvers whose input features are all known until all requested outputs are computed or no further progress can be made. - For the cases in the tests below, successfully compute `User.is_fraud` from `User.id` by chaining through the appropriate resolvers. Skeleton and tests: ```python def execute(inputs: dict[Feature | Any, Any], outputs: list[Feature | Any]) -> dict[Feature, Any]: """Implement me! Example: execute(inputs={User.id: 1}, outputs=[User.is_fraud]) == {User.is_fraud: False} """ return {} def test_execute(): assert execute( inputs={User.id: 1}, outputs=[User.is_fraud], ) == {User.is_fraud: False} assert execute( inputs={User.id: 2}, outputs=[User.is_fraud], ) == {User.is_fraud: True} test_execute() ``` Your task: implement `features`, `resolver`, and `execute` so that all the provided tests (`test_scalars`, `test_forward_reference`, `test_properties`, `test_resolvers`, and `test_execute`) pass without modifying the tests themselves.

Quick Answer: This question evaluates proficiency in Python metaprogramming, specifically use of decorators, runtime type introspection, handling of type-annotated classes and forward references, and design of feature/resolver abstractions.

Related Interview Questions

  • Compute rainwater accumulation on a height grid - Chalk (medium)
Chalk logo
Chalk
Sep 27, 2025, 12:00 AM
Software Engineer
Technical Screen
Coding & Algorithms
55
0

You are given a skeleton for a small Python "feature" framework. The goal is to use metaprogramming to:

  1. Parse type-annotated classes into Feature objects via a @features decorator.
  2. Parse functions into Resolver objects via a @resolver decorator.
  3. Implement an execute function that, given some known input feature values and desired output features, automatically computes the outputs by chaining resolvers.

You must implement the parts marked Implement me! so that all the tests at the bottom pass.

You may find the following tools useful (but they are not required):

  • cls.__annotations__
  • cls.__name__
  • setattr(obj, key, value)
  • inspect.signature(fn)
  • typing.get_type_hints

Step 1: Parse features from annotated classes

Implement the @features class decorator so that:

  • For each type-annotated attribute in the decorated class, you create a Feature instance with:
    • name equal to "<ClassName>.<field_name>" (for example, "User.name" ).
    • typ equal to the Python type of that attribute (resolving forward references like the string annotation "User" to the actual class User ).
  • The decorated class has a class attribute features which is a list of all Feature instances for that class in declaration order.
  • Each annotated attribute of the class (e.g., User.id , User.email ) evaluates to a Feature instance with the corresponding name and typ , so that expressions like User.id == Feature(name="User.id", typ=int) are true.

Skeleton and tests:

import dataclasses

@dataclasses.dataclass(unsafe_hash=True)
class Feature:
    name: str  # e.g. "User.name"
    typ: type  # e.g. str


def features(cls):
    """Class decorator. Implement me!

    After decoration:
    - <Class>.features is a list[Feature]
    - <Class>.<field_name> is a Feature instance
    """
    return cls


@features
class Card:
    id: int
    number: str
    owner: "User"   # forward reference


@features
class User:
    id: int
    email: str
    name: str
    card_id: int
    is_fraud: bool


def test_scalars():
    assert User.features == [
        Feature(name="User.id", typ=int),
        Feature(name="User.email", typ=str),
        Feature(name="User.name", typ=str),
        Feature(name="User.card_id", typ=int),
        Feature(name="User.is_fraud", typ=bool),
    ], User.features


def test_forward_reference():
    assert Card.features == [
        Feature(name="Card.id", typ=int),
        Feature(name="Card.number", typ=str),
        Feature(name="Card.owner", typ=User),  # Note: typ should be the class User, not the string "User"
    ], Card.features


def test_properties():
    assert User.id == Feature(name="User.id", typ=int)


test_scalars()
test_forward_reference()
test_properties()

Step 2: Parse resolvers from functions

Resolvers are functions that compute the value of one Feature from other Features. You must implement the @resolver decorator so that it:

  • Wraps the original function in a Resolver dataclass instance.
  • Extracts the input Feature objects from the function's parameter annotations in order.
  • Extracts the output Feature from the function's return annotation.
  • Stores the original function in Resolver.fn so that it can be called later.

Assume resolver functions are annotated using Feature objects like User.id, User.name etc., for both parameters and return type.

Skeleton and tests:

import inspect
import dataclasses
from typing import Callable, Any, TypeVar, ParamSpec, Generic

P = ParamSpec("P")
T = TypeVar("T")


@dataclasses.dataclass
class Resolver(Generic[P, T]):
    inputs: list[Feature]
    output: Feature
    fn: Callable[P, T]


def resolver(fn: Callable[P, T]) -> Resolver[P, T]:
    """Function decorator. Implement me!

    Should return a Resolver instance whose:
    - inputs: parameter annotations (Feature objects) in order.
    - output: return annotation (a Feature object).
    - fn: the original function.
    """
    return fn


@resolver
def get_user_name(id: User.id) -> User.name:
    if id == 1:
        return "elliot"
    return "joe"


@resolver
def get_user_email(id: User.id) -> User.email:
    if id == 1:
        return "elliot@chalk.ai"
    return "fraudster@chalk.ai"


@resolver
def get_user_fraud_score(name: User.name, email: User.email) -> User.is_fraud:
    return name.lower() not in email.lower()


def test_resolvers():
    assert get_user_name.output == User.name, get_user_name.output
    assert get_user_name.inputs == [User.id], get_user_name.inputs

    assert get_user_email.output == User.email, get_user_email.output
    assert get_user_email.inputs == [User.id], get_user_email.inputs

    assert get_user_fraud_score.output == User.is_fraud
    assert get_user_fraud_score.inputs == [User.name, User.email]


test_resolvers()

You may additionally choose to register all created Resolver instances in some global registry so that execute (in Step 3) can find them, but the exact registration mechanism is up to you as long as the behavior matches the tests.

Step 3: Implement the execution engine

Implement the execute function that computes desired output features from a set of known inputs by chaining together resolvers defined using @resolver.

Requirements:

  • inputs is a dictionary mapping either:
    • Feature objects to their concrete values (e.g., {User.id: 1} ), or
    • optionally, any alternative keys you choose to support, as long as the provided tests pass.
  • outputs is a list of requested outputs, each being either a Feature or something you map to a Feature .
  • execute returns a dictionary {Feature: value} for all requested output features.
  • It must:
    • Use the registered Resolver objects created by the @resolver decorator.
    • Repeatedly apply resolvers whose input features are all known until all requested outputs are computed or no further progress can be made.
    • For the cases in the tests below, successfully compute User.is_fraud from User.id by chaining through the appropriate resolvers.

Skeleton and tests:

def execute(inputs: dict[Feature | Any, Any], outputs: list[Feature | Any]) -> dict[Feature, Any]:
    """Implement me!

    Example:
    execute(inputs={User.id: 1}, outputs=[User.is_fraud]) == {User.is_fraud: False}
    """
    return {}


def test_execute():
    assert execute(
        inputs={User.id: 1},
        outputs=[User.is_fraud],
    ) == {User.is_fraud: False}

    assert execute(
        inputs={User.id: 2},
        outputs=[User.is_fraud],
    ) == {User.is_fraud: True}


test_execute()

Your task: implement features, resolver, and execute so that all the provided tests (test_scalars, test_forward_reference, test_properties, test_resolvers, and test_execute) pass without modifying the tests themselves.

Comments (0)

Sign in to leave a comment

Loading comments...

Browse More Questions

More Coding & Algorithms•More Chalk•More Software Engineer•Chalk Software Engineer•Chalk Coding & Algorithms•Software Engineer Coding & Algorithms
PracHub

Master your tech interviews with 7,500+ real questions from top companies.

Product

  • Questions
  • Learning Tracks
  • Interview Guides
  • Resources
  • Premium
  • For Universities
  • Student Access

Browse

  • By Company
  • By Role
  • By Category
  • Topic Hubs
  • SQL Questions
  • Compare Platforms
  • Discord Community

Support

  • support@prachub.com
  • (916) 541-4762

Legal

  • Privacy Policy
  • Terms of Service
  • About Us

© 2026 PracHub. All rights reserved.