Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Python expiry exercise: separate pending, active and expired records at exact boundaries

Last updated: 30 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

An expiry classifier assigns records to time states using declared inclusive and exclusive boundaries.

Download Python source kit

Operation contract

The exercise accepts an owned list of at most sixty-four unique receipt records and a bounded exact-integer clock. A record is pending before its creation time, active from creation through the instant before created plus TTL, and expired at or after that endpoint. Validate every record before returning any result. A duplicate identity or Boolean TTL fails the batch instead of quietly choosing one record. Results are sorted IDs so caller row order cannot change their presentation.

Failure and ownership boundary

The clock and record times use the same abstract integer unit; the fixture does not interpret wall-clock zones or accept serialized timestamps. It never deletes or renews records. Python TTL cache: expire at a defined boundary and use an injected clock, Python datetime: require an offset before comparing timestamps and Python reconciliation exercise: reject duplicate IDs before comparing ledgers add those policies separately. Test the exact creation and expiry instants, not only values comfortably inside the interval.

Working program

python
def classify_expiry(records, now):
    if type(now) is not int or not 0 <= now <= 1000000 or type(records) is not list or len(records) > 64:
        raise ValueError("bounded records and clock required")
    seen, states = set(), {"pending": [], "active": [], "expired": []}
    for record in records:
        if type(record) is not dict or set(record) != {"receipt_id", "created", "ttl"}:
            raise ValueError("exact fields required")
        identifier, created, ttl = record["receipt_id"], record["created"], record["ttl"]
        if type(identifier) is not str or not identifier.isascii() or not identifier.isalnum() or not 1 <= len(identifier) <= 16 or identifier in seen or type(created) is not int or not 0 <= created <= 1000000 or type(ttl) is not int or not 1 <= ttl <= 100:
            raise ValueError("unique ID and bounded times required")
        seen.add(identifier)
        state = "pending" if now < created else "active" if now < created + ttl else "expired"
        states[state].append(identifier)
    return {state: sorted(identifiers) for state, identifiers in states.items()}

records = [{"receipt_id": "R41", "created": 100, "ttl": 10},
           {"receipt_id": "R42", "created": 110, "ttl": 5},
           {"receipt_id": "R43", "created": 120, "ttl": 5}]
print(classify_expiry(records, 110))
try:
    classify_expiry(records + [dict(records[0])], 110)
except ValueError:
    print("duplicate batch rejected")

Output

Output
{'pending': ['R43'], 'active': ['R42'], 'expired': ['R41']}
duplicate batch rejected

Costs and limits

Validation and classification take O(n), with up to O(n log n) sorting and O(n) private result storage. The supplied records are left unchanged even on rejection. This is a snapshot calculation; applying its result to mutable service state later would require a concurrency policy.

Common Mistakes

  • State whether the expiry endpoint is active or expired.
  • Reject duplicate identities before returning a batch result.

Connected lessons

Python TTL cache: expire at a defined boundary and use an injected clock, Python reconciliation exercise: reject duplicate IDs before comparing ledgers, Python datetime: require an offset before comparing timestamps.

Check this related boundary

Python testing: inject a clock to test expiry at the exact boundary.

python
expiry-exercise
Storage details