cswork / libraryOpen practice workspace

Interview guides · Anthropic

Anthropic Software Engineer Interview Guide 2026

Prepare for Anthropic SWE interviews with a tested rate limiter, tier changes, concurrency, distributed-system trade-offs and a focused four-week plan.

Interviewing at Anthropic

  • Practice bank: 213+ questions
  • Rounds: 4
  • Typical prep: 2–4 weeks
  • Interview reports: 36

Interview rounds

RoundQuestions
HR Screen13
Online Assessment1
Technical Screen60
Onsite139

How hard is the Anthropic Software Engineer interview?

DifficultyShareQuestions
Easy1%2
Medium61%129
Hard38%82

What Anthropic actually tests for

TopicShareQuestions
Coding & Algorithms35%75
System Design22%46
Behavioral & Leadership20%43
Software Engineering Fundamentals11%23
ML System Design9%20
Machine Learning2%4
Analytics & Experimentation1%2

A rate limiter is easy to sketch until the requirements change. Two requests can race for the last slot. A tier downgrade can leave more recent requests than the new limit allows. A network timeout can make it unclear whether an admission was recorded. Those are useful engineering discussions to rehearse for an Anthropic Software Engineer interview.

This revision keeps the original guide's focus on adaptable implementation, systems reasoning, project ownership and mission discussion. It replaces the short rate-limiter sketch with an original runnable exercise, exact boundary examples and checked follow-ups. The mock is preparation material, not Anthropic's implementation or a prediction of your interview.

Use the Anthropic Software Engineer question bank to choose a practice task relevant to your recruiter instructions. Attempt it before opening the solution and record the first requirement change that breaks your design.

What current official guidance confirms

Anthropic's careers page describes remote interviews on Google Meet, live technical environments such as Colab and CodeSignal, and discussion of experience and motivation. It allows looking things up while recommending fluency with syntax and standard libraries. The page does not establish one universal SWE stage count, final-loop length or decision deadline.

The candidate AI policy makes a separate distinction. Preparation is encouraged, and AI can refine an application draft you wrote yourself. Take-home assessments and live interviews do not permit AI unless Anthropic explicitly says otherwise. Permission to consult documentation is not blanket permission for an AI-generated solution.

Confirm your assessment format, language, permitted tools, design scope and any project presentation with recruiting. References and team discussions may be relevant to your process; ask whether and when they apply rather than assuming the earlier guide's sequence is fixed.

The public careers material also emphasizes simple effective solutions, honest communication and responsibility for consequences. Prepare actual examples of a decision, an uncertainty, the evidence you gathered and what you changed. These themes support a useful preparation approach; they are not access to an unpublished scoring rubric.

Original coding exercise: a sliding-window limiter

Allow at most L accepted requests for a user in a window of W integer ticks. At time t, the window is (t − W, t]: an event exactly W ticks old has expired. Rejected requests do not consume capacity. Users have independent limits, and a configured limit of zero blocks admission.

Stage one handles one user. Stage two adds separate users. Stage three changes limits without resetting history. Stage four makes the check safe for concurrent calls in one process. The distributed follow-up is a design discussion beyond the local implementation.

Clarify the following choices before typing:

QuestionContract chosen for this mock
ClockNonnegative integer ticks, globally nondecreasing across calls
Duplicate timestampsValid; each accepted call counts separately
Unknown userReject with KeyError until configured
UpgradeMore capacity immediately, with existing history retained
DowngradePreserve accepted history; block until count is below new limit
Denial resultFalse and earliest retry tick under unchanged limit and no future admissions
Zero limitFalse and no calculable retry tick

This request-count sliding window differs from Anthropic's documented API token-bucket rate limits. The real API has organization and workspace rules and multiple usage dimensions. Do not use this exercise's timestamps, thresholds or retry result as API guidance.

Trace a tier upgrade and downgrade

Set W to 10 and a user's limit to 2. Requests at ticks 0 and 1 are accepted. Tick 2 is denied with retry tick 10; it does not enter the queue. Upgrade the limit to 3, then accept tick 3. The accepted history is [0, 1, 3].

Downgrade to 1. Tick 4 is denied until 13. Waiting until the oldest event expires at 10 is insufficient: [1, 3] still exceeds the capacity. At 11, [3] remains and fills the one allowed slot. At 13 it expires, so a new request is accepted.

Now set the limit to zero and deny tick 14 without clearing the accepted tick 13. Restore limit 1 and deny tick 15 until 23. A tier change must not accidentally manufacture an empty history or a fresh burst.

Runnable local implementation

The invariant is that each user's queue contains accepted timestamps in nondecreasing order. Before an admission decision, remove expired timestamps. Under the same lock, read the current limit, check the remaining count and append only if capacity is available.

from collections import deque
from threading import Lock

class SlidingLimiter:
    def __init__(self, window):
        if type(window) is not int or window <= 0:
            raise ValueError("window must be a positive integer")
        self._window = window
        self._limits = {}
        self._events = {}
        self._last_now = -1
        self._lock = Lock()

    @staticmethod
    def _user(user):
        if not isinstance(user, str) or not user:
            raise ValueError("user must be nonempty text")

    def set_limit(self, user, limit):
        self._user(user)
        if type(limit) is not int or limit < 0:
            raise ValueError("limit must be a nonnegative integer")
        with self._lock:
            self._limits[user] = limit
            self._events.setdefault(user, deque())

    def allow(self, user, now):
        self._user(user)
        if type(now) is not int or now < 0:
            raise ValueError("now must be a nonnegative integer tick")
        with self._lock:
            limit = self._limits[user]  # Unknown users raise KeyError.
            if now < self._last_now:
                raise ValueError("clock must not go backwards")
            self._last_now = now
            events = self._events[user]
            while events and events[0] <= now - self._window:
                events.popleft()
            if limit == 0:
                return False, None
            if len(events) >= limit:
                retry_at = events[len(events) - limit] + self._window
                return False, retry_at
            events.append(now)
            return True, None

For a denied request with H retained events and positive limit L, the next possible admission requires H − L + 1 events to expire. Their zero-based final index is H − L. That is why the retry timestamp is not always the oldest event plus the window, especially after a downgrade.

The return value assumes the limit stays fixed and no intervening request takes the freed slot. It is a useful hint, not a reservation. Two clients can retry at the same tick and only one may be admitted.

The global clock constraint keeps this exercise deterministic. Real callers should not supply arbitrary client wall-clock times to an authoritative limiter. A service needs a trusted time source and a policy for clock behavior; a distributed design cannot compare unrelated process-monotonic origins as if they were one shared timestamp.

Demonstrate the exact boundary

s = SlidingLimiter(10)
s.set_limit("user", 2)
assert s.allow("user", 0) == (True, None)
assert s.allow("user", 1) == (True, None)
assert s.allow("user", 2) == (False, 10)
s.set_limit("user", 3)
assert s.allow("user", 3) == (True, None)
s.set_limit("user", 1)
assert s.allow("user", 10) == (False, 13)
assert s.allow("user", 11) == (False, 13)
assert s.allow("user", 13) == (True, None)

Local verification executed 2,333 checks. Fixed cases include exact expiry, retained history after upgrades and downgrades, zero capacity, separate users, invalid input and backwards-clock rejection. A list-based independent model checks 1,000 generated operations with tier changes, repeated timestamps and advancing time.

In 100 controlled races, two threads attempt the last available slot at the same tick. Each trial verifies one acceptance, one denial, one stored timestamp and termination of both threads. These checks establish local behavior for this class; no Redis, HTTP, API token accounting or employer assessment was performed.

Complexity and limits of this implementation

Each admitted timestamp is appended once and removed at most once, so queue maintenance is amortized constant work per event. Dictionary lookup is expected constant time. But a deque's indexed access can be linear away from an end; calculating a retry after a large downgrade can therefore cost O(H). Do not label the entire method unconditionally O(1).

Stored events are bounded by prior admissions under the limits that applied then, not necessarily by today's lower limit. The implementation retains configured users indefinitely and only cleans a user's queue when that user calls. An inactive user's old records remain in memory. Long-running services need bounded key retention and careful cleanup rules.

The single lock makes configuration changes and admissions easy to reason about, but unrelated users wait for it too. Per-user locks could reduce contention while requiring safe lock creation, deletion and configuration ownership. Choose that complexity only after identifying a workload that needs it. These tests do not measure throughput or contention at production scale.

Distributed follow-up: preserve one atomic decision

Moving the queue behind a Store interface is useful only if its contract preserves correctness. A distributed operation must atomically read the applicable configuration, remove expired records, check capacity and record a successful admission. Separate read-count and append requests recreate the same last-slot race across servers.

One design can use a Redis script over a user's timestamp set and versioned configuration. Redis documents atomic execution of scripts, which also blocks other activity while the script runs. Keep work bounded. For a clustered store, ensure all keys needed by the operation are supported together; atomicity does not magically span independent shards.

Duplicate timestamps require unique event identifiers. If a timestamp is the sole sorted-set member, two accepted calls at the same time can collapse into one entry. Choose a unique admission identifier and store timestamp as its score. Define retention, clock authority and maximum cleanup work rather than translating the deque mechanically.

Atomic execution is also not a durability promise. Discuss failover and lost writes against the required strictness. A response timeout can mean the decision ran but the client did not receive it. Retrying with a new identifier can consume another slot. A request-ID receipt may make admission retries stable, but admitting once does not itself guarantee an external business action happens once.

Configuration changes need an explicit effective point. If limits live in a separate cache with eventual updates, different servers may apply different tiers. Use a shared authoritative configuration revision in the atomic operation, or document the permitted delay. The local set_limit and allow lock represents a single ordering only inside this process.

Outage policy: choose by the protected operation

“Fail open or fail closed?” has no universal answer. A free preview endpoint might accept limited traffic through a separate emergency local cap during a dependency outage. A costly job-submission endpoint might reject new admissions to protect compute capacity. Authentication and authorization checks should not disappear merely because the rate-limit store is down.

State the objective, tolerated overshoot, duration and monitoring. If every server uses an emergency cap of E requests, fleet capacity can reach approximately server-count × E over the window; a local fallback does not preserve a global cap. Label those values hypothetical until the workload is measured.

Rate limits also do not replace queue capacity, cancellation, backpressure or a spend budget. An admitted request may be extremely expensive. For an inference service, reason separately about request count, estimated input work, generated output, concurrent execution and final accounting. Avoid holding the limiter's local lock around a network call or a model run.

Prepare a project discussion and mission answer

Choose a system you actually worked on. Explain its users, constraints, your specific responsibility, the rejected alternative, the measurement and one failure. If a result was estimated rather than observed, say so. If you inherited the design, distinguish the part you changed from the part you only operated.

For a safety or reliability discussion, describe a concrete boundary: permissions, data exposure, rollout control, monitoring or recovery. Explain who made the decision and the cost of the safer option. A rehearsed statement about being interested in AI cannot substitute for a real example of careful engineering.

Prepare a disagreement where new evidence changed your view. State the opposing hypothesis fairly, the smallest useful check and the resulting decision. Use your own experience rather than adopting an invented interview success story. The same habit helps when an interviewer changes a requirement mid-problem: revise the assumption and tests openly.

A four-week preparation plan

This is an original practice schedule. It is not the employer's recruiting timeline, and the workload should change after your baseline reveals a gap.

WeekWorkEvidence to produce
1Staged coding in the permitted languageContract, runnable baseline, boundary tests, two follow-ups
2Systems reasoningAtomic operation, clock model, retry and outage decisions
3Real projects and mission discussionOwnership map, measurement, limitation and changed decision
4Mocks and targeted repairTimed recordings, missed requirements and retested fixes

In a coding mock, spend the first few minutes on an example and invariant. Implement the baseline, run it, then add a requirement without abandoning earlier cases. In a design mock, draw the critical decision path and one failed-response timeline before adding technology names.

If time is short, prioritize the assessment your recruiter actually assigned and the weakest demonstrated skill. Ask for the next-step window and whether references or team matching remain. Public guide estimates cannot tell you when your application will receive a decision.

Sources and scope

Reviewed October 8, 2026. Official sources establish public hiring and technology context.