← Apple Interview Insights

Apple·Software Engineer·Technical Phone Screen·Intermediate

IntermediatePrefer not to say
Jul 2026

Summary

Apple SWE interview with an object-oriented design question centered on a deck of cards. More depth required than I expected, especially around probability correctness and the shuffle algorithm.

Questions Asked (5)

Q1

Design an in-memory model of a standard 52-card deck that supports shuffle() and draw() operations, where shuffle() produces an unbiased ordering and draw() returns each remaining card with equal probability.

Algorithms & Data StructuresSystem DesignTechnical Trade-offs
Author's notes

I jumped straight into defining a Card class and felt pretty good about that part.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying requirements and constraints, then propose a data structure (e.g., array of cards) and algorithms for shuffle (Fisher-Yates) and draw (swap with last and pop). Discuss trade-offs between different approaches and analyze time/space complexity to ensure unbiased and efficient operations.

Pro tip: Mention that using a cryptographically secure random number generator (CSPRNG) is important for fairness in real-world card games, and discuss how to handle edge cases like drawing from an empty deck.

1. Clarify Requirements and Constraints

Ask questions to understand expected operations, performance needs, and any constraints (e.g., thread safety, memory). Confirm that shuffle should be unbiased and draw should return each remaining card with equal probability.

2. Choose Data Structure and Card Representation

Decide on a simple representation: an array or list of 52 card objects (e.g., integers or structs with suit and rank). Explain why an array is suitable for O(1) random access and efficient shuffling.

3. Implement Shuffle with Fisher-Yates

Describe the Fisher-Yates (Knuth) shuffle algorithm: iterate from the last index down to 1, swap the current element with a randomly chosen element from the remaining unshuffled portion. This guarantees an unbiased permutation in O(n) time.

4. Implement Draw with Swap-and-Pop

For draw(), select a random index from the current deck size, swap that card with the last card, then remove and return the last card. This ensures each remaining card has equal probability and keeps the deck compact.

5. Analyze Complexity and Discuss Trade-offs

State time and space complexity: shuffle is O(n), draw is O(1), space is O(n). Discuss alternatives (e.g., using a linked list for draw but slower shuffle) and the importance of a good RNG for fairness.

Key Points to Mention

  • Fisher-Yates shuffle algorithm and its unbiased nature
  • Time and space complexity: O(n) for shuffle, O(1) for draw, O(n) space
  • Use of a cryptographically secure random number generator for fairness
  • Handling edge cases: drawing from an empty deck, resetting the deck
  • Trade-offs between array-based and other data structures (e.g., linked list)
  • Thread safety considerations if the deck is shared across threads

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q2

What clarifying questions would you ask before starting the design, and how would the answers change your implementation?

System DesignTechnical Trade-offsAdaptability & Ambiguity
Author's notes

Asked about empty deck behavior and whether shuffle resets to 52 cards.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by acknowledging that clarifying questions are essential to avoid building the wrong thing, then walk through a structured set of questions covering requirements, scale, constraints, and priorities. For each question, explain how the answer would directly influence your design decisions, such as technology choices, architecture, or trade-offs.

Pro tip: Tie your questions to Apple's values: privacy, seamless user experience, and performance. For example, ask about data sensitivity to determine encryption needs, or about latency targets to decide between edge and cloud processing.

1. Clarify Functional Requirements

Ask about the core features, user personas, and expected behaviors. This determines the system's scope and primary components.

2. Identify Non-Functional Requirements

Inquire about scale (users, requests per second), latency, availability, consistency, and durability. These drive architectural patterns and technology choices.

3. Understand Constraints and Priorities

Ask about budget, timeline, team expertise, existing systems, and regulatory/compliance needs. This shapes build vs. buy decisions and trade-offs.

4. Map Answers to Design Decisions

For each answer, explicitly state how it changes your implementation—e.g., high read scale leads to caching and read replicas; strong consistency requires consensus protocols.

5. Summarize and Confirm

Recap the key assumptions and their implications, and confirm with the interviewer that your understanding is correct before proceeding.

Key Points to Mention

  • Scale: number of users, requests per second, data volume, growth projections
  • Latency and throughput requirements: p99 latency, real-time vs. batch processing
  • Consistency and availability trade-offs: CAP theorem implications, eventual vs. strong consistency
  • Data privacy and security: encryption, access controls, compliance (GDPR, HIPAA)
  • Budget and resource constraints: cost per user, infrastructure limits, team size
  • Integration with existing systems: APIs, legacy databases, third-party services

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q3

How would you unit test that your shuffle is actually unbiased, given that the output is random?

Algorithms & Data StructuresTechnical Trade-offs
Author's notes

Blanked for a second.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying that you can't test a single output for bias, but you can test the distribution of many outputs using statistical hypothesis testing. Propose a chi-squared test on the frequency of each element at each position, and also test for uniformity of permutations. Mention that you'd use a fixed seed for reproducibility and run enough trials to achieve statistical power.

Pro tip: Don't just test the marginal distributions—also test for correlations between positions and ensure the shuffle passes standard randomness test suites like Dieharder or NIST. This shows you understand that bias can hide in dependencies.

1. Define the null hypothesis and test statistic

State that the shuffle is unbiased if every permutation is equally likely. Choose a test statistic, such as the chi-squared statistic for the observed frequencies of each element at each position, or for the frequencies of entire permutations.

2. Generate many shuffled outputs with a fixed seed

Run the shuffle algorithm many times (e.g., 100,000) on a small array (e.g., 3-5 elements) using a deterministic random seed for reproducibility. Record the results.

3. Apply statistical tests

Perform chi-squared goodness-of-fit tests for uniformity of element positions and for uniformity of permutations. Also consider tests for independence between positions, such as mutual information or correlation coefficients.

4. Interpret p-values and account for multiple testing

Use a significance level (e.g., 0.05) and correct for multiple comparisons (e.g., Bonferroni). If p-values are not significant, fail to reject the null hypothesis; if significant, investigate potential bias.

5. Complement with known randomness test suites

Run the shuffle output through standard randomness test suites like NIST SP 800-22 or Dieharder to catch subtle biases that simple chi-squared tests might miss.

Key Points to Mention

  • Chi-squared test for uniformity of element positions and permutations
  • Fixed seed for reproducibility and control over random number generation
  • Multiple testing correction (e.g., Bonferroni) to avoid false positives
  • Testing for independence between positions (e.g., mutual information)
  • Standard randomness test suites (NIST, Dieharder) for comprehensive validation
  • Statistical power and sample size considerations to detect small biases

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q4

How would you extend this design to support a multi-deck shoe and a discard pile, with a reshuffle that folds discards back in?

System DesignData Modeling
Author's notes

Follow-up question.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the current design and requirements, then propose extending the data model with a multi-deck shoe and a discard pile, and define the reshuffle logic that merges discards back into the shoe. Emphasize modularity, testability, and edge cases like penetration threshold and shuffle randomness.

Pro tip: Discuss how you would make the reshuffle deterministic for testing by injecting a seedable RNG, and mention the importance of atomic operations if the game state is shared across threads.

1. Clarify current design and requirements

Ask about the existing card representation, shoe implementation, and game flow to understand what needs extension. Confirm requirements like number of decks, reshuffle trigger (e.g., penetration), and whether discards include burned cards.

2. Extend data model for multi-deck shoe and discard pile

Represent the shoe as a collection of cards from multiple decks, and the discard pile as a separate collection. Consider using a deque or list for efficient draw and discard operations.

3. Define reshuffle logic and trigger

Specify when reshuffling occurs (e.g., when shoe size falls below a threshold). Implement a method that combines remaining shoe cards with discards, shuffles them, and resets the discard pile.

4. Address edge cases and concurrency

Handle cases like empty shoe, discard pile during reshuffle, and thread safety if multiple players act concurrently. Ensure the reshuffle is atomic and does not disrupt ongoing hands.

5. Discuss testing and extensibility

Outline unit tests for draw, discard, and reshuffle scenarios. Mention how the design supports future extensions like multiple shoes or different reshuffle policies.

Key Points to Mention

  • Use of appropriate data structures (e.g., deque for shoe, list for discard pile) for O(1) draw and discard.
  • Reshuffle trigger based on penetration (e.g., when 75% of cards are dealt) and how to calculate it.
  • Ensuring randomness and fairness in shuffling, possibly using Fisher-Yates with a secure RNG.
  • Separation of concerns: shoe manages cards, discard pile tracks used cards, and a reshuffle service coordinates.
  • Thread safety and atomicity if the game state is accessed concurrently.
  • Testability: injecting a mock RNG for deterministic tests and simulating edge cases.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q5

If two threads draw from the same deck concurrently, how do you ensure no card is dealt twice and the draw remains uniform?

System DesignTechnical Trade-offs
Author's notes

Talked about putting a lock around the draw operation.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Clarify the concurrency model and constraints, then propose a thread-safe deck abstraction using atomic operations or locks to ensure each card is drawn exactly once. Discuss how to maintain uniform randomness by shuffling once and using an atomic index, and analyze trade-offs between lock-based and lock-free approaches.

Pro tip: Mention that a single shuffle followed by atomic index increments is both efficient and uniform, and that lock-free approaches can avoid contention but require careful memory ordering. Also note that the deck should be immutable after shuffling to prevent data races.

1. Clarify requirements and constraints

Ask about the expected concurrency level, performance needs, and whether the deck can be modified. Confirm that each card must be dealt exactly once and that the draw order should be uniformly random.

2. Design a thread-safe deck

Propose representing the deck as an array of cards shuffled once at initialization. Use an atomic integer as an index to track the next card to deal, ensuring atomic fetch-and-increment operations.

3. Ensure uniform randomness

Explain that shuffling the deck uniformly (e.g., Fisher-Yates) before any draws guarantees that each permutation is equally likely. The atomic index then deals cards in that random order without introducing bias.

4. Handle synchronization and contention

Discuss using atomic operations for the index to avoid locks, or alternatively a mutex if simplicity is preferred. Analyze trade-offs: atomics reduce contention but require careful memory ordering; locks are simpler but may serialize access.

5. Address edge cases and scalability

Consider what happens when the deck is exhausted, and how to handle multiple decks or dynamic resizing. Mention that the approach scales well with multiple threads due to minimal contention.

Key Points to Mention

  • Atomic operations (e.g., fetch-and-add) for the deck index to ensure each card is drawn exactly once.
  • Shuffling the deck once upfront using a uniform random permutation (Fisher-Yates) to maintain uniformity.
  • Trade-offs between lock-based (mutex) and lock-free (atomic) synchronization: simplicity vs. performance.
  • Memory ordering and visibility concerns in lock-free implementations (e.g., using std::atomic with appropriate memory order).
  • Immutability of the deck after shuffling to prevent data races and ensure consistency.
  • Handling deck exhaustion and potential need for synchronization when refilling or resetting.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.