Machine Coding Problem

Shopping Cart (Guest Merge)

macoAllcommerceguest-vs-logged-in-merge
Commonly Asked By:AmazonWalmartShopify

Requirements & System Scope

Functional Specifications (In-Scope)

  • Guest vs Logged-In Modes: Decouples anonymous session carts stored in-memory from user account carts stored in the user profile registry.
  • Pluggable Conflict Resolution: Configurable merge policies (KEEP_GUEST, KEEP_USER, SUM_QUANTITIES, PREFER_HIGHER_QTY).
  • Real-Time Inventory Validation: Verifies stock balances on merge, automatically downsizing or deleting out-of-stock items.
  • User Alert Dispatcher: Captures stock discrepancy modifications to push notification alerts to logged-in users.

Out-of-Scope Boundaries

  • Payment Integration Gateways: Excludes checkout transactions to focus strictly on session and inventory reconciliations.
  • Persistent Cookie Store TTLs: Relies on simple React states to represent short-term cookie memory.

Class Diagram & Entity Relationships

Entity blueprint mapping session carts, user records, and stock checkers:

Loading...
  • Cart & CartItem: Models generic collection bins with quantity and unit cost fields.
  • InventoryService: Checks product counts before finalizing user balances.
  • CartMergeService: Clears session logs after copying active balances.

Design Patterns & SOLID Principles

  • Strategy Pattern: Encapsulates different conflict resolution merge strategies under a clean strategy policy selector.
  • Facade Pattern: Exposes a single unified interface (merge) that encapsulates the complexity of inventory checking, quantity adjustments, notifications generating, and session clearing.
  • Dependency Inversion Principle (DIP): Ensures CartMergeService depends on abstract inventory validation lookups, decoupled from backend database details.

Core Execution Workflows

Guest Merge & Stock Check Execution Workflow

  1. Trigger Authentication Login:
    1. Load current GuestCart associated with anonymous sessionId.
    2. Load target UserCart associated with authenticated userId.
  2. Merge & Conflict Resolution:
    1. For each product in guest cart: if it also exists in user cart, resolve quantities using policy (e.g., max(GuestQty, UserQty)).
    2. If the product only exists in guest cart, add to target list.
  3. Real-time Stock Constraints:
    1. Query available stock. If stock is 0, delete item from cart and push an alert: “Removed item (Out of Stock)”.
    2. If stock is less than resolved quantity, downscale quantity to matches available stock and push warning alert.
    3. Clean up session cart and save merged products to persistent user database.

Concurrency & Thread Safety Strategy

Securing carts under heavy simultaneous merges (e.g. user logs in on both phone and browser at once):

  • Concurrent Hash Maps: Prevents deadlocks during asynchronous session lookups.
  • Pessimistic Inventory Locking: Locks inventory records when checking stock balances to prevent double-allocating limited products during simultaneous checkouts.

Complete Clean Code Blueprint

Production reference implementations demonstrating conflict solvers, inventory integrations, and clean cleanup mechanisms in Java and Python:

// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;

enum MergePolicy {
    KEEP_GUEST,
    KEEP_USER,
    SUM_QUANTITIES,
    PREFER_HIGHER_QTY
}

class CartItem {
    private final String productId;
    private int quantity;
    private final double unitPrice;
    private final long addedAt;

    public CartItem(String productId, int quantity, double unitPrice) {
        this.productId = productId;
        this.quantity = quantity;
        this.unitPrice = unitPrice;
        this.addedAt = System.currentTimeMillis();
    }

    public String getProductId() { return productId; }
    public int getQuantity() { return quantity; }
    public double getUnitPrice() { return unitPrice; }
    public long getAddedAt() { return addedAt; }

    public void setQuantity(int quantity) { this.quantity = quantity; }
}

class Cart {
    private final String cartId; // sessionId or userId
    private final Map<String, CartItem> items = new ConcurrentHashMap<>();
    private long lastUpdated;

    public Cart(String cartId) {
        this.cartId = cartId;
        this.lastUpdated = System.currentTimeMillis();
    }

    public String getCartId() { return cartId; }
    public Map<String, CartItem> getItems() { return items; }
    public long getLastUpdated() { return lastUpdated; }

    public void addItem(String productId, int qty, double price) {
        items.merge(productId, new CartItem(productId, qty, price), (oldVal, newVal) -> {
            oldVal.setQuantity(oldVal.getQuantity() + qty);
            return oldVal;
        });
        lastUpdated = System.currentTimeMillis();
    }

    public void clear() {
        items.clear();
        lastUpdated = System.currentTimeMillis();
    }
}

class InventoryService {
    private final Map<String, Integer> stockRegistry = new ConcurrentHashMap<>();

    public void setStock(String productId, int qty) {
        stockRegistry.put(productId, qty);
    }

    public int getAvailableStock(String productId) {
        return stockRegistry.getOrDefault(productId, 0);
    }
}

class MergeResult {
    private final Cart mergedCart;
    private final List<String> notifications;

    public MergeResult(Cart mergedCart, List<String> notifications) {
        this.mergedCart = mergedCart;
        this.notifications = notifications;
    }

    public Cart getMergedCart() { return mergedCart; }
    public List<String> getNotifications() { return notifications; }
}

class CartMergeService {
    private final Map<String, Cart> guestCarts = new ConcurrentHashMap<>();
    private final Map<String, Cart> userCarts = new ConcurrentHashMap<>();
    private final InventoryService inventoryService;

    public CartMergeService(InventoryService inventoryService) {
        this.inventoryService = inventoryService;
    }

    public Cart getGuestCart(String sessionId) {
        return guestCarts.computeIfAbsent(sessionId, Cart::new);
    }

    public Cart getUserCart(String userId) {
        return userCarts.computeIfAbsent(userId, Cart::new);
    }

    public MergeResult merge(String sessionId, String userId, MergePolicy policy) {
        Cart guestCart = guestCarts.get(sessionId);
        Cart userCart = getUserCart(userId);
        List<String> notifications = new ArrayList<>();

        if (guestCart == null || guestCart.getItems().isEmpty()) {
            return new MergeResult(userCart, notifications);
        }

        // Apply merge logic
        for (CartItem guestItem : guestCart.getItems().values()) {
            String prodId = guestItem.getProductId();
            CartItem userItem = userCart.getItems().get(prodId);

            int targetQty = 0;
            if (userItem == null) {
                targetQty = guestItem.getQuantity();
            } else {
                switch (policy) {
                    case KEEP_GUEST:
                        targetQty = guestItem.getQuantity();
                        break;
                    case KEEP_USER:
                        targetQty = userItem.getQuantity();
                        break;
                    case SUM_QUANTITIES:
                        targetQty = userItem.getQuantity() + guestItem.getQuantity();
                        break;
                    case PREFER_HIGHER_QTY:
                        targetQty = Math.max(userItem.getQuantity(), guestItem.getQuantity());
                        break;
                }
            }

            // Inventory Validation Check
            int availableStock = inventoryService.getAvailableStock(prodId);
            if (availableStock <= 0) {
                notifications.add("Removed product: " + prodId + " (Out of Stock)");
                userCart.getItems().remove(prodId);
            } else if (targetQty > availableStock) {
                notifications.add("Reduced qty of: " + prodId + " from " + targetQty + " to " + availableStock + " due to limited stock.");
                if (userItem != null) {
                    userItem.setQuantity(availableStock);
                } else {
                    userCart.addItem(prodId, availableStock, guestItem.getUnitPrice());
                }
            } else {
                if (userItem != null) {
                    userItem.setQuantity(targetQty);
                } else {
                    userCart.addItem(prodId, targetQty, guestItem.getUnitPrice());
                }
            }
        }

        // Clean up Session Guest Cart
        guestCarts.remove(sessionId);

        return new MergeResult(userCart, notifications);

    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println("=== JAVA SHOPPING CART MERGE SIMULATION ===");
        InventoryService inventory = new InventoryService();
        inventory.setStock("laptop", 2);
        inventory.setStock("headphones", 0);
        inventory.setStock("keyboard", 5);

        CartMergeService service = new CartMergeService(inventory);

        // Guest session adding items
        String sessionId = "guest-session-999";
        Cart guestCart = service.getGuestCart(sessionId);
        guestCart.addItem("laptop", 3, 1200.0);
        guestCart.addItem("headphones", 1, 250.0);

        // Authenticated user cart has some existing items
        String userId = "user-alice";
        Cart userCart = service.getUserCart(userId);
        userCart.addItem("laptop", 1, 1200.0);
        userCart.addItem("keyboard", 1, 150.0);

        System.out.println("Guest Cart Qty of laptop: " + guestCart.getItems().get("laptop").getQuantity());
        System.out.println("User Cart Qty of laptop: " + userCart.getItems().get("laptop").getQuantity());

        // Perform merge
        System.out.println("\nLogging in guest to merge carts with PREFER_HIGHER_QTY policy...");
        MergeResult result = service.merge(sessionId, userId, MergePolicy.PREFER_HIGHER_QTY);

        System.out.println("Merge complete. Notifications:");
        for (String note : result.getNotifications()) {
            System.out.println(" - " + note);
        }

        System.out.println("\nFinal Merged User Cart Items:");
        for (CartItem item : result.getMergedCart().getItems().values()) {
            System.out.println("Product: " + item.getProductId() + ", Qty: " + item.getQuantity() + ", Price: $" + item.getUnitPrice());
        }
        System.out.println("=== END OF JAVA SIMULATION ===");
    }
}

Interactive Simulator

❖ Guest & Logged-in Cart Merger Simulator

Configure stock balances, choose dynamic merge policies, simulate user logins, and resolve out-of-stock discrepancies on the fly.

🛒 Anonymous Guest Cart (Session)Active Session

Pro LaptopQty: 3
Noise Cancelling HeadphonesQty: 1
Wireless MouseQty: 2

👤 Authenticated User Cart (Alice)Persistent Store

Pro LaptopQty: 1
Mechanical KeyboardQty: 1

Merge Trigger Panel

Inventory Registry Status

Configure stock levels on the fly to see how the merge service prevents double-booking or out-of-stock errors.

Pro Laptop
Cost: $1200
2
Noise Cancelling Headphones
Cost: $250
0
Mechanical Keyboard
Cost: $150
5
Wireless Mouse
Cost: $80
10
Add Items to Carts:
Pro Laptop
Noise Cancelling Headphones
Mechanical Keyboard
Wireless Mouse

System Reconcile Notifications

Trigger login to run reconciliation.

💬Review

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...