Skip to main content

AI-Assisted Feature Flag Interview Playbook

Goal​

Use Claude as a bounded implementation and reasoning assistant while you remain the driver of the interview.

The strongest signal is not:

"Claude solved the problem."

The stronger signal is:

"I decomposed the problem, identified ambiguous semantics, used Claude to investigate one uncertainty at a time, validated each milestone, and owned every implementation decision."


Problem Background

You are given three JSON files and asked to implement a feature flag evaluation engine that maps each user to the final boolean value of every flag.

Input Files​

flags.json​

Contains definitions for 7 feature flags.

Example:

{
"key": "beta_dashboard",
"enabled": true,
"default": false,
"rules": [
{
"name": "market_launch",
"conditions": [
{
"attribute": "country",
"operator": "in",
"value": ["US", "CA"]
},
{
"attribute": "plan",
"operator": "eq",
"value": "enterprise"
}
],
"value": true
}
]
}

user.json​

Contains approximately 10,000 users.

Typical fields:

{
"user_id": "12345",
"email": "user@example.com",
"country": "US",
"plan": "enterprise",
"support_tier": "premium"
}

expected_decisions.json​

Contains ground-truth decisions for approximately:

200 users × 7 flags = 1,400 decisions

Use this file as an oracle to infer ambiguous semantics and validate implementation correctness.


Confirmed Semantics

Disabled Flag​

If:

{
"enabled": false
}

return the flag's default immediately.

Do not evaluate rules.


Rule Conditions​

Conditions inside one rule use logical AND.

condition A
AND
condition B
AND
condition C

All must match.


No Matching Rules​

Return:

flag["default"]

Multiple Matching Rules​

If multiple rules match:

false
false
true
false

the final result is:

true

This is true-wins aggregation.

Do not assume:

  • first matching rule wins
  • last matching rule wins
  • alphabetical rule order
  • rule-name priority

Operators

Known operators include:

eq
in
regex
~=

Important:

  • regex is anchored like re.match
  • ~= semantics must be verified from fixture data
  • unknown operators should fail safely
  • missing attributes should not crash evaluation

Percentage Rollout

Some rules include:

{
"percentage": 30
}

Do not blindly assume how the percentage bucket is calculated.

Possible interpretations include:

user_id % 100
hash(user_id) % 100
hash(flag_key + user_id) % 100
input ordering
another deterministic strategy

Use expected_decisions.json to infer the correct behavior before locking in the implementation.


Dirty Data Trap

Some users may contain:

{
"country": "",
"plan": ""
}

These may have appeared after the expected decisions were generated.

Do not immediately assume every mismatch involving empty data is an evaluator bug.

Distinguish:

A. evaluator bug
B. dirty fixture data
C. expected data generated from an earlier clean dataset

60-Minute Interview Plan

TimeMilestone
00–05Understand requirements and define milestones
05–10Inspect fixtures and resolve semantics
10–18Implement evaluate_condition
18–24Implement rule_matches
24–32Implement evaluate_flag
32–38Implement orchestration + oracle harness
38–45Diagnose mismatches
45–50Add regression tests
50–55Cleanup + code review
55–60Explain architecture, complexity, and tradeoffs

Step 1 — 0–5 Minutes

Restate the Problem​

Say:

"I'm going to separate this into condition evaluation, rule evaluation, flag aggregation, percentage rollout, and oracle validation. I'll use Claude incrementally to challenge assumptions and help with small implementation steps rather than asking it to generate the whole solution."

Claude Prompt​

I'm implementing a feature-flag evaluator from three JSON files:

- flags.json
- user.json
- expected_decisions.json

Known requirements:

- disabled flag => return default immediately
- conditions within a rule use AND
- no matching rules => default
- multiple matching rules => true wins
- operators: eq, in, regex, ~=
- some rules contain percentage rollout
- expected_decisions.json contains oracle results

Do NOT implement yet.

Give me:

1. a compact list of confirmed semantics
2. ambiguous semantics I must verify from the fixtures
3. a minimal implementation plan with milestones
4. the 5 highest-risk edge cases

Keep this concise because this is a 60-minute coding interview.

Milestones​

M1 inspect data
M2 condition evaluator
M3 rule evaluator
M4 flag evaluator
M5 oracle validation
M6 percentage / ~= resolution
M7 edge cases + cleanup

Step 2 — 5–10 Minutes

Inspect the Fixtures​

Focus only on ambiguous semantics.

Claude Prompt​

Inspect these fixtures with me.

I need evidence for only these questions:

1. What exactly does ~= mean?
2. How is percentage rollout determined?
3. Can multiple rules match the same user?
4. Confirm that true wins when matched rules conflict.
5. What shapes do missing or empty user fields take?

Do not generate implementation code.

For each question, show the smallest concrete fixture examples that answer it.

Percentage Investigation Prompt​

Percentage rollout is underspecified.

Do not assume an industry-standard hash.

Using expected_decisions.json, determine whether the observed results are consistent with:

- user_id % 100
- hash(user_id) % 100
- hash(flag_key + user_id) % 100
- input-order percentage
- another obvious deterministic strategy

Tell me what is proven versus still uncertain.

Strong Interview Signal​

Say:

"The percentage field is underspecified, so I don't want to guess whether it uses user ID hashing, flag-scoped hashing, or ordering. I'll use the oracle first."


Step 3 — 10–18 Minutes

Implement evaluate_condition

Target:

evaluate_condition(user, condition) -> bool

Claude Prompt​

Current milestone: implement only

evaluate_condition(user, condition) -> bool

Operators:

- eq
- in
- regex
- ~=

Known semantics from fixtures:
[paste what we established]

Requirements:

- missing attributes must not throw
- regex uses anchored matching
- unknown operators should fail safely

Do not build rules or flags yet.

Give me:

1. short pseudocode
2. edge cases
3. 6 table-driven tests
4. then a minimal Python implementation

Step 4 — Challenge Claude Before Accepting Code

Use Claude as a reviewer.

Review this condition evaluator.

Do NOT rewrite it yet.

Find:

- incorrect coercion
- missing/null/empty-string problems
- regex mistakes
- incorrect ~= semantics
- cases where an exception could escape

Give me only correctness issues and tests that expose them.

Target checkpoint:

eq ✅
in ✅
regex ✅
~= ✅ or explicitly isolated

Step 5 — 18–24 Minutes

Implement rule_matches

Target:

rule_matches(user, flag, rule)

Claude Prompt​

Next milestone: rule evaluation.

I already have evaluate_condition().

Required rule semantics:

- all conditions must match: logical AND
- percentage rollout should be evaluated only after conditions match
- empty/malformed conditions should fail safely according to fixture behavior

Review this decomposition:

rule_matches(user, flag, rule)
-> conditions_match
-> percentage_check

Give me:

1. semantic problems with this split
2. 4 focused tests
3. minimal Python code only for this layer

Do not implement evaluate_flag yet.

Empty Conditions Trap​

Python:

all([])

returns:

True

So explicitly investigate the meaning of an empty conditions array.

Prompt​

For an empty conditions array, Python all([]) returns True.

Based on feature-flag semantics and the fixtures, should an empty-condition rule represent an unconditional rule or an invalid rule?

Use fixture evidence if available. Do not guess.

Step 6 — 24–32 Minutes

Implement evaluate_flag

Target:

evaluate_flag(user, flag) -> bool

Expected Flow​

flag disabled?
|
yes
|
return default

no
|
evaluate matching rules
|
any TRUE matching rule?
|
yes ---> return True
|
no
|
did any rule match?
/ \
yes no
| |
False default

Claude Prompt​

Next milestone: evaluate a single flag.

Confirmed semantics:

1. if enabled == false -> return default immediately
2. evaluate matching rules
3. if none match -> return default
4. if multiple match -> true wins
5. percentage eligibility is part of rule matching

I want:

evaluate_flag(user, flag) -> bool

Before code:

- state the control-flow invariant
- identify whether true-wins lets us short-circuit
- give 5 tests

Then give the smallest readable Python implementation with comments explaining WHY, not obvious syntax.

Step 7 — Try to Break evaluate_flag

Prompt:

Try to break evaluate_flag.

Focus on:

- default=true with matching false rule
- default=false with no match
- one false + one true rule
- disabled flag with otherwise matching true rule
- rules with percentage exclusions

Do not refactor unless you find a correctness issue.

Step 8 — 32–38 Minutes

Implement Orchestration

Target:

evaluate_all_flags(user, flags)
evaluate_all_users(users, flags)

These functions should contain no feature-flag semantics.

Claude Prompt​

Core flag evaluation is implemented.

Now add only orchestration:

evaluate_all_flags(user, flags)
evaluate_all_users(users, flags)

No feature semantics should leak into these functions.

Then create a validation harness against expected_decisions.json that reports:

- passed / total
- accuracy
- mismatch records containing user_id, flag, expected, actual

Keep it simple and interview-friendly.

Step 9 — Oracle Validation

Run all expected decisions.

Target:

1,400 comparisons

Possible result:

Passed: 1378 / 1400

Do not polish yet.

Analyze failures first.


Step 10 — 38–45 Minutes

Cluster Failures Instead of Patching

Bad prompt:

Fix my failures.

Better prompt:

Oracle result:

passed: 1378 / 1400

Here are the mismatches:
[paste mismatches]

Do NOT modify code yet.

Cluster failures by likely root cause.

Return a table:

cluster | count | shared characteristics | likely violated invariant | smallest diagnostic experiment

Prefer one semantic explanation that accounts for many failures over individual patches.

Scenario — Rollout Failures

Focus only on the rollout failure cluster.

Current implementation:
[paste rollout function]

Observed mismatches:
[paste cases]

Expected:
...

Actual:
...

Give me:

1. 3 hypotheses
2. which fixture examples distinguish them
3. the smallest experiment
4. only then the minimal fix

Scenario — Dirty Data Failures

All remaining failures involve country="" or plan="".

I do not want to change core semantics unless necessary.

Help me distinguish:

A. evaluator bug
B. dirty fixture data
C. expected_decisions generated before dirty fields were introduced

Give me evidence and a minimal diagnostic.

Strong interviewer statement:

"I don't want to contaminate clean evaluation semantics with a workaround until I know whether the mismatch belongs to the evaluator or the fixture."


General Debugging Prompt

Use this structure repeatedly:

Expected:
...

Actual:
...

Relevant input:
...

Current invariant:
...

Current implementation:
...

Do not rewrite the entire solution.

Please:

1. identify the most likely violated invariant
2. propose 2-3 hypotheses
3. suggest the smallest diagnostic experiment
4. only after that suggest the minimal code change

Scenario — Claude Generates Too Much Code

Redirect it.

Stop before implementation.

You're solving too much at once.

For this step I only need:

- contract
- invariants
- edge cases
- tests

I will implement the code myself.

Or:

Reduce this to the smallest change required for the current milestone.

Do not refactor unrelated code.

Scenario — Claude Makes an Unsupported Assumption

Example:

"Use hash(userId) % 100."

Respond:

That's plausible, but it is not established by the requirements.

Do not treat industry convention as evidence.

Show me what observations from expected_decisions.json distinguish:

hash(userId)
from
hash(flagKey + userId)
from
userId % 100

I want to infer behavior before implementing it.

Scenario — 10–20 Tests Still Fail

Avoid:

fix the failures

Use:

Current accuracy: 1387 / 1400.

I don't want 13 ad-hoc patches.

Analyze the failures for a shared pattern.

Create a table:

failure cluster | count | likely cause | evidence | diagnostic

Prefer one semantic correction that explains many failures over case-specific fixes.

Scenario — Claude Proposes a Large Refactor

This change is larger than I want during an interview.

Preserve:

- public function signatures
- current passing behavior
- existing tests

Suggest the smallest delta that fixes the identified invariant.

Show changed lines conceptually before generating code.

Step 11 — 45–50 Minutes

Regression Tests

Prompt:

Core evaluator now matches the oracle on clean data.

Generate a compact pytest regression suite covering only high-value semantics:

1. disabled flag returns default
2. conditions use AND
3. no match returns default
4. true-wins conflicts
5. regex is anchored
6. ~= behavior
7. rollout 0 / boundary / 100
8. missing/empty user attributes
9. default=true + matched false rule

Keep tests small and readable.

If time is short, simple asserts are enough.

assert evaluate_flag(user, flag) is True

Say:

"Given the interview timebox, I'll keep tests lightweight but still cover semantic boundaries."


Step 12 — 50–55 Minutes

Cleanup and Code Review

Prompt:

The implementation passes the oracle and targeted tests.

Do a final interview-oriented code review.

Priorities, in order:

1. correctness
2. readability
3. unnecessary duplication
4. obvious performance issues
5. comments explaining non-obvious semantics

Do NOT introduce new abstractions unless they materially simplify the code.
Do NOT change public function signatures.
Do NOT rewrite working logic unnecessarily.

Give me a short list of changes ranked:

must-fix
optional

Overengineering Check​

Check specifically for accidental overengineering.

There are only 7 flags and 10,000 users.

Tell me what abstractions I should NOT add.

Avoid unnecessary:

classes
factories
dependency injection
visitor patterns
complex rule engines

A simple functional design is enough.


Step 13 — 55–60 Minutes

Explain the Solution

Use this structure.

Architecture​

"The evaluator is decomposed into condition, rule, flag, and orchestration layers so each semantic unit can be tested independently."

Disabled Flags​

"A disabled flag returns its default immediately and skips all rule evaluation."

Rules​

"Conditions inside a rule are ANDed."

Conflict Resolution​

"When multiple rules match, any true rule wins, so the evaluator can short-circuit once it finds a matching true rule."

Oracle​

"I used expected_decisions.json as an oracle for underspecified behavior rather than guessing."

Percentage Rollout​

"I isolated percentage bucketing because rollout semantics were ambiguous and should be inferred from the fixture."

Complexity​

For:

U = users
F = flags
R = average rules per flag
C = average conditions per rule

runtime is approximately:

O(U × F × R × C)

For:

10,000 users
7 flags
small rule sets

this is easily acceptable.


Final 90-Second Claude Prompt

I need to explain this solution in under 90 seconds.

Summarize:

- decomposition
- semantics
- complexity
- why the oracle was useful
- one scaling improvement

Keep it technically precise and concise.

Recommended Implementation Architecture

load_data()

evaluate_condition(user, condition)
├── eq
├── in
├── regex
└── approximate

is_user_in_rollout(flag, user)

rule_conditions_match(user, rule)

rule_matches(user, flag, rule)

evaluate_flag(user, flag)

evaluate_all_flags(user, flags)

evaluate_all_users(users, flags)

validate_results(...)

Python Reference Solution

import json
import re
import hashlib
from typing import Any, Dict, List


def evaluate_condition(
user: Dict[str, Any],
condition: Dict[str, Any],
) -> bool:
"""
Evaluate one condition against one user.

Supported operators:
- eq
- in
- regex
- ~=

Missing values fail safely.
"""

attribute = condition.get("attribute")
operator = condition.get("operator")
expected = condition.get("value")

actual = user.get(attribute)

if actual is None:
return False

if operator == "eq":
return actual == expected

if operator == "in":
if not isinstance(expected, (list, tuple, set)):
return False

return actual in expected

if operator == "regex":
try:
# re.match is anchored at the beginning.
return re.match(str(expected), str(actual)) is not None
except re.error:
return False

if operator == "~=":
return approximate_match(actual, expected)

return False


def approximate_match(
actual: Any,
expected: Any,
) -> bool:
"""
Placeholder interpretation.

Replace this behavior if expected_decisions.json proves
a different ~= semantic.
"""

if (
isinstance(actual, (int, float))
and isinstance(expected, (int, float))
):
return abs(actual - expected) < 1e-9

return str(expected).lower() in str(actual).lower()


def rule_conditions_match(
user: Dict[str, Any],
rule: Dict[str, Any],
) -> bool:
"""
Conditions within one rule use logical AND.
"""

conditions = rule.get("conditions", [])

return all(
evaluate_condition(user, condition)
for condition in conditions
)


def stable_bucket(
flag_key: str,
user_id: Any,
) -> int:
"""
Deterministically maps a flag + user into [0, 99].

IMPORTANT:
Use this only if fixture analysis confirms this hashing strategy.
"""

raw = f"{flag_key}:{user_id}".encode("utf-8")

digest = hashlib.sha256(raw).hexdigest()

return int(digest[:8], 16) % 100


def passes_percentage_rollout(
user: Dict[str, Any],
flag: Dict[str, Any],
rule: Dict[str, Any],
) -> bool:
"""
Evaluates optional percentage rollout.
"""

percentage = rule.get("percentage")

if percentage is None:
return True

try:
percentage = float(percentage)
except (TypeError, ValueError):
return False

if percentage <= 0:
return False

if percentage >= 100:
return True

user_id = user.get("user_id")

if user_id is None:
return False

bucket = stable_bucket(
flag_key=flag.get("key", ""),
user_id=user_id,
)

return bucket < percentage


def rule_matches(
user: Dict[str, Any],
flag: Dict[str, Any],
rule: Dict[str, Any],
) -> bool:
"""
A rule matches when:

1. every condition matches
2. optional percentage rollout includes the user
"""

if not rule_conditions_match(user, rule):
return False

if not passes_percentage_rollout(user, flag, rule):
return False

return True


def evaluate_flag(
user: Dict[str, Any],
flag: Dict[str, Any],
) -> bool:
"""
Evaluate one feature flag for one user.

Semantics:

1. Disabled -> default immediately
2. Evaluate all applicable rules
3. No matching rules -> default
4. Any matching true rule -> true
5. Only matching false rules -> false
"""

default = bool(flag.get("default", False))

# Disabled flags ignore all rules.
if not flag.get("enabled", False):
return default

matched = False

for rule in flag.get("rules", []):
if not rule_matches(user, flag, rule):
continue

matched = True

# True-wins allows early termination.
if rule.get("value") is True:
return True

if matched:
return False

return default


def evaluate_all_flags(
user: Dict[str, Any],
flags: List[Dict[str, Any]],
) -> Dict[str, bool]:
"""
Evaluate every flag for one user.
"""

return {
flag["key"]: evaluate_flag(user, flag)
for flag in flags
}


def evaluate_all_users(
users: List[Dict[str, Any]],
flags: List[Dict[str, Any]],
) -> Dict[str, Dict[str, bool]]:
"""
Evaluate every flag for every user.
"""

results = {}

for user in users:
user_id = str(user["user_id"])

results[user_id] = evaluate_all_flags(
user,
flags,
)

return results

Load JSON Files

def load_json(path: str):
with open(path, "r", encoding="utf-8") as file:
return json.load(file)


flags = load_json("flags.json")
users = load_json("user.json")
expected = load_json("expected_decisions.json")

Validation Harness

def validate_results(
users,
flags,
expected,
):
users_by_id = {
str(user["user_id"]): user
for user in users
}

total = 0
passed = 0
failures = []

for user_id, expected_flags in expected.items():

user = users_by_id.get(str(user_id))

if user is None:
failures.append({
"user_id": user_id,
"error": "user not found",
})

continue

actual_flags = evaluate_all_flags(
user,
flags,
)

for flag_key, expected_value in expected_flags.items():

total += 1

actual_value = actual_flags.get(flag_key)

if actual_value == expected_value:
passed += 1

else:
failures.append({
"user_id": user_id,
"flag": flag_key,
"expected": expected_value,
"actual": actual_value,
})

print(f"Passed: {passed}/{total}")

if total:
print(f"Accuracy: {passed / total:.2%}")

return failures

Usage:

failures = validate_results(
users,
flags,
expected,
)

for failure in failures[:20]:
print(failure)

Important Optimization

Because conflict behavior is:

true wins

you can short-circuit.

Instead of collecting all rule values:

matched_values = []

you can do:

for rule in rules:

if not rule_matches(...):
continue

matched = True

if rule["value"] is True:
return True

This is simpler and faster.


Potential Operator Registry

If operators grow significantly, refactor:

if operator == "eq":
...

if operator == "in":
...

if operator == "regex":
...

into:

OPERATORS = {
"eq": evaluate_eq,
"in": evaluate_in,
"regex": evaluate_regex,
"~=": evaluate_approximate,
}

But do not introduce this abstraction prematurely.

With four operators, a simple conditional implementation is perfectly acceptable.


High-Value Test Cases

Disabled Flag​

enabled = false
default = true
matching true rule exists

expected = true

Rules must be ignored.


No Match​

enabled = true
default = false
no rules match

expected = false

AND Conditions​

country = US ✅
plan = free ❌

rule requires:
country = US
AND
plan = enterprise

expected rule match = false

True Wins​

rule A => false
rule B => true

both match

expected = true

Matching False Overrides Default True​

Important edge case:

default = true

matching rule => false

expected = false

This verifies:

no match -> default

is not the same as:

all matched rules are false -> default

Regex Anchoring​

If:

pattern = abc
actual = xyzabc

with anchored matching:

expected = false

But:

actual = abcxyz

may match.


Percentage Boundaries​

percentage = 0
=> false

percentage = 100
=> true

Also verify deterministic bucket boundaries.


Dirty Data​

Test:

{
"country": "",
"plan": ""
}

Ensure evaluation does not crash.


Strong Interview Narration

Use short comments while working.

Before Coding​

"I'm separating semantic discovery from implementation because the fixtures contain behavior that isn't fully specified."

Before Condition Evaluator​

"I'll start with the smallest semantic primitive so failures remain easy to localize."

Before Percentage Logic​

"I don't want to invent rollout semantics, so I'll derive them from the oracle."

After Partial Oracle Results​

"Rather than patching individual failures, I'll cluster mismatches and look for a shared violated invariant."

Before Cleanup​

"Now that behavior is validated, I'll make small readability improvements without risking a broad refactor."


Best AI Workflow

Observe
↓
Form hypothesis
↓
Ask Claude to challenge it
↓
Define invariant
↓
Implement smallest unit
↓
Run focused tests
↓
Run oracle comparison
↓
Cluster mismatches
↓
Fix root cause
↓
Repeat

Weak AI Workflow

Avoid:

requirements
↓
"Claude, solve this"
↓
500 lines of generated code
↓
debug blindly

Reusable Claude Interview Prompt Template

Keep this in a scratch file.

CURRENT MILESTONE

I am implementing:
[one small capability]

KNOWN SEMANTICS

- ...
- ...
- ...

UNRESOLVED QUESTION

[one ambiguity only]

CURRENT INVARIANT

[what must remain true]

CURRENT CODE

[small relevant code only]

OBSERVATION

Expected:
...

Actual:
...

YOUR TASK

Do not solve the whole problem.

1. Check whether my reasoning is sound.
2. Identify assumptions I'm making.
3. Give the smallest experiment that resolves the uncertainty.
4. Suggest focused tests.
5. Only provide implementation changes if the semantics are sufficiently established.
6. Preserve already-passing behavior.

Final Interview Checklist

Requirements​

  • Disabled flag behavior understood
  • Default behavior understood
  • AND semantics confirmed
  • Conflict resolution confirmed
  • Regex semantics confirmed
  • ~= semantics confirmed
  • Percentage rollout semantics confirmed

Implementation​

  • evaluate_condition
  • rule_conditions_match
  • passes_percentage_rollout
  • rule_matches
  • evaluate_flag
  • evaluate_all_flags
  • evaluate_all_users

Validation​

  • Run expected decisions
  • Report passed / total
  • Cluster mismatches
  • Investigate dirty data separately
  • Add regression tests

Final Review​

  • Remove unnecessary abstractions
  • Keep semantic comments
  • Explain complexity
  • Explain oracle usage
  • Mention one scaling improvement

What the Interviewer Should See

The interviewer should observe this pattern:

You choose the milestone
↓
You state the invariant
↓
Claude challenges assumptions
↓
Claude helps with a bounded step
↓
You inspect the output
↓
You run validation
↓
You interpret failures
↓
You choose the next step

The goal is to make it clear that Claude is your engineering assistant, not the owner of the solution.