cswork / libraryOpen practice workspace

Coding questions · Capital One

Debug and test a Python function in venv

This question evaluates understanding of Python runtime semantics (mutable default arguments, in-place list mutation during iteration), debugging and unit-testing practices, virtual environment and packaging workflows, and CI/tooling considerations.

Consider the Python snippet below.

""" import math

def accumulate(nums, start=0, cache={}):
 total = start
 for n in nums:
 if n % 2 == 0:
 nums.append(n//2)
 total += n
 cache[str(nums)] = total
 return total

"""

Tasks:

  1. Precisely state what accumulate([2, 3], start=1) returns and why (walk through iteration order and mutations). Identify every bug/code smell (logical, algorithmic, and design) and their impact: list-mutation during iteration, default-mutable arguments, potential non-termination, unintended cache growth, time/space complexity, and keying strategy.
  2. Provide a corrected, production-ready version (pure function + optional caching) with asymptotic complexity analysis and docstring that specifies pre/post-conditions.
  3. Write minimal but thorough pytest unit tests (property-based or table-driven) covering edge cases (empty list, very large even chains, negatives, None, non-ints) and proving termination.
  4. Show exact commands to: create/activate an isolated virtual environment; pin dependencies; run tests; and produce a wheel. Assume macOS/Linux.
  5. Briefly explain how you would add CI (static type checks with mypy, linting, coverage thresholds) and package an entry-point CLI.

Part 1: Simulate the buggy accumulate function and report the design issues

You are given the behavior of this buggy Python function:

import math

def accumulate(nums, start=0, cache={}):
    total = start
    for n in nums:
        if n % 2 == 0:
            nums.append(n // 2)
        total += n
        cache[str(nums)] = total
    return total

For this problem, assume nums contains only integers and the cache is empty at the start of the analyzed call. Return a 4-tuple:

  1. whether the call terminates, 2) the exact return value if it terminates, otherwise None, 3) the final mutated nums list if it terminates, otherwise None, 4) a fixed sorted list of issue codes describing the bugs/code smells in the function.

Use Python's real list-iteration behavior: elements appended during iteration are visited later in the same loop. If execution would never terminate, return False for the first field.

Constraints

  • 0 <= len(nums) <= 10^5
  • -10^18 <= nums[i], start <= 10^18
  • nums contains only integers
  • If 0 appears in nums, the original function would not terminate

Examples

Example 1

Input: ([2, 3], 1)
Output: (True, 7, [2, 3, 1], ['default_mutable_cache', 'input_list_mutation_during_iteration', 'keying_by_stringified_mutable_list', 'potential_nontermination_when_zero_present', 'quadratic_time_from_repeated_stringify', 'unbounded_cache_growth_across_calls'])

Explanation: 2 is visited first, so 1 is appended. The loop then visits 3 and finally the appended 1. Total = 1 + 2 + 3 + 1 = 7.

Example 2

Input: ([], 5)
Output: (True, 5, [], ['default_mutable_cache', 'input_list_mutation_during_iteration', 'keying_by_stringified_mutable_list', 'potential_nontermination_when_zero_present', 'quadratic_time_from_repeated_stringify', 'unbounded_cache_growth_across_calls'])

Explanation: The loop body never runs, so the function returns start unchanged.

Hints

  1. In Python, a for-loop over a list can visit items appended before the loop ends.
  2. Among integers, repeatedly applying n // 2 to an even number only loops forever for 0.

Part 2: Implement a pure, terminating accumulate with optional caching

Implement a corrected version of accumulate.

For each integer n in nums, define its contribution as:

  • include n itself,
  • while the current value is nonzero and even, replace it with current // 2 and include that new value too.

Examples:

  • 3 contributes 3
  • 4 contributes 4 + 2 + 1 = 7
  • -2 contributes -2 + -1 = -3
  • 0 contributes 0 exactly once

Return start plus the sum of all contributions. The function must be pure: it must not mutate nums. If use_cache is True, reuse previously computed contributions for repeated numbers within the same call. Return None for invalid input: nums is None, nums is not a list, start is not an int, use_cache is not a bool, or any element of nums is not a plain int (bool is invalid).

Constraints

  • 0 <= len(nums) <= 10^5 for valid cases
  • -10^18 <= nums[i], start <= 10^18 for valid cases
  • bool values are considered invalid even though bool is a subclass of int in Python

Hints

  1. Process each number independently; you do not need to append into the original list.
  2. A repeated number should have the same contribution every time, which makes memoization natural.

Part 3: Choose a minimal table-driven pytest suite

You are designing a minimal but thorough pytest suite for the corrected accumulate function. Each candidate test covers one or more behavior tags such as 'empty', 'chain', 'negative', 'none', 'nonint', and 'termination'.

Given a list of candidate tests and a list of required tags, choose the smallest subset of test names whose combined tags cover every required tag.

If multiple minimum-size subsets exist, return the lexicographically smallest sorted list of test names. If it is impossible to cover all required tags, return None.

Constraints

  • 1 <= number of candidates <= 20
  • 0 <= number of required tags <= 15
  • Candidate names are unique strings

Examples

Example 1

Input: ([('test_empty', ['empty', 'termination']), ('test_chain', ['chain', 'termination']), ('test_neg', ['negative']), ('test_invalid', ['none', 'nonint']), ('test_mixed', ['negative', 'chain'])], ['empty', 'chain', 'negative', 'none', 'nonint', 'termination'])
Output: ['test_empty', 'test_invalid', 'test_mixed']

Explanation: This is the unique minimum cover of size 3.

Example 2

Input: ([('aa_empty_term', ['empty', 'termination']), ('ab_invalid_chain', ['none', 'nonint', 'chain']), ('ac_negative', ['negative']), ('ba_empty_chain', ['empty', 'chain']), ('bb_invalid_term', ['none', 'nonint', 'termination']), ('bc_negative', ['negative'])], ['empty', 'chain', 'negative', 'none', 'nonint', 'termination'])
Output: ['aa_empty_term', 'ab_invalid_chain', 'ac_negative']

Explanation: There are two minimum covers of size 3; this one is lexicographically smaller.

Hints

  1. Map each required tag to a bit position, then each test becomes a bitmask.
  2. Dynamic programming over covered-tag masks can find the minimum-size subset with deterministic tie-breaking.

Part 4: Generate exact venv, dependency, test, and wheel build commands

Generate the exact macOS/Linux shell command sequence for a Python project.

Input gives:

  • project_dir: project folder name
  • runtime_deps: list of (package_name, version)
  • dev_deps: list of (package_name, version)

Return the ordered list of commands that:

  1. enters the project directory, 2) creates a virtual environment named .venv, 3) activates it, 4) upgrades pip, 5) installs all pinned dependencies, 6) freezes the environment to requirements-lock.txt, 7) runs pytest quietly, 8) builds a wheel.

Rules:

  • Always ensure pytest==8.2.2 and build==1.2.1 are installed.
  • Package names are deduplicated case-insensitively.
  • If the same package appears multiple times, keep the highest semantic version.
  • Sort installed packages alphabetically by normalized lowercase package name.

Constraints

  • 0 <= total dependency entries <= 200
  • Versions use numeric dot-separated semantic versioning such as 1.2.3
  • Package names are non-empty strings without spaces

Examples

Example 1

Input: ("app", [('requests', '2.32.3')], [('pytest', '8.1.1')])
Output: ['cd app', 'python3 -m venv .venv', 'source .venv/bin/activate', 'python -m pip install --upgrade pip', 'python -m pip install build==1.2.1 pytest==8.2.2 requests==2.32.3', 'python -m pip freeze > requirements-lock.txt', 'pytest -q', 'python -m build --wheel']

Explanation: pytest is bumped to the required pinned version, and build is added.

Example 2

Input: ("proj", [('Requests', '2.31.0'), ('requests', '2.32.3')], [('build', '1.0.0'), ('flake8', '7.1.0')])
Output: ['cd proj', 'python3 -m venv .venv', 'source .venv/bin/activate', 'python -m pip install --upgrade pip', 'python -m pip install build==1.2.1 flake8==7.1.0 pytest==8.2.2 requests==2.32.3', 'python -m pip freeze > requirements-lock.txt', 'pytest -q', 'python -m build --wheel']

Explanation: requests is deduplicated case-insensitively and the higher version is kept.

Hints

  1. Normalize package names to lowercase before deduplicating.
  2. Compare semantic versions component by component, not as raw strings.

Part 5: Build a deterministic CI plan and CLI entry-point mapping

You are configuring CI and packaging for a Python project.

Given a list of requested features from {'lint', 'mypy', 'coverage', 'cli'}, produce:

  • an ordered list of CI/build commands, and
  • the CLI entry-point string if CLI packaging is requested.

Rules:

  • If features is empty, return ([], None).
  • If an unknown feature appears, return None.
  • If 'coverage' is requested, coverage_threshold must be an integer in [0, 100].
  • If 'cli' is requested, both cli_name and cli_target must be non-empty.

Model the workflow as a DAG:

  • setup is required whenever any feature is enabled.
  • lint depends on setup.
  • mypy depends on setup.
  • test depends on setup and represents the coverage run.
  • build depends on setup and also on every enabled check among lint, mypy, and test.

Use alphabetical order when multiple nodes are ready at the same time.

Command mapping:

  • setup -> 'python -m pip install -e .[dev]'
  • lint -> 'ruff check .'
  • mypy -> 'mypy src'
  • test -> 'pytest --cov=src --cov-fail-under={coverage_threshold}'
  • build -> 'python -m build'

If 'cli' is enabled, return entry point '{cli_name}={cli_target}', otherwise return None.

Constraints

  • 0 <= len(features) <= 4
  • features contains strings
  • coverage_threshold is only validated when 'coverage' is enabled

Examples

Example 1

Input: (['lint', 'mypy', 'coverage', 'cli'], 90, 'accumulate', 'pkg.cli:main')
Output: (['python -m pip install -e .[dev]', 'ruff check .', 'mypy src', 'pytest --cov=src --cov-fail-under=90', 'python -m build'], 'accumulate=pkg.cli:main')

Explanation: setup runs first, then lint/mypy/test in alphabetical order, and build runs last.

Example 2

Input: (['cli'], 80, 'tool', 'app.main:run')
Output: (['python -m pip install -e .[dev]', 'python -m build'], 'tool=app.main:run')

Explanation: Only setup and build are needed.

Hints

  1. Treat each CI action as a graph node and use topological sorting.
  2. A deterministic tie-break rule is needed whenever several jobs become available together.