tesco interview experience

tesco logo
tesco
May 9, 20252 reads

Summary

This post details a system design interview experience at Tesco, where I was tasked with building a real-time analytics dashboard backend service that integrates data from multiple disparate internal services, handles various data formats and reliability issues, and provides an aggregated API endpoint.

Full Experience

You're developing a backend service for a real-time analytics dashboard that tracks user interactions across an e-commerce platform. Your service needs to:

  • Process incoming event data from multiple sources
  • Perform aggregation operations
  • Provide API endpoints for the frontend dashboard with summarized data (given below)

Core Requirements:

Your service needs to fetch and combine data from three internal data sources:

  • User Activity Service: Tracks page views, clicks, and session data
  • Transaction Service: Tracks purchases, cart additions, and checkout events
  • Inventory Service: Tracks product stock levels and alerts

Note that each service has different response characteristics, data formats, and reliability issues:

/**
 * Fetches recent user activity data
 * Returns detailed user interaction events
 * High volume data, occasionally returns partial results
 * ~150ms average response time with occasional spikes
 */
async function fetchUserActivity(timeWindow) {
    return new Promise((resolve, reject) => {
        const delay = 100 + Math.random() * 100; // 100-200ms
        setTimeout(() => {
            if (Math.random() < 0.15) {
                // Sometimes returns partial data with an error flag
                resolve({
                    events: generateRandomEvents(Math.floor(Math.random() * 10) + 5),
                    complete: false,
                    error: "Service degraded, partial data returned"
                });
            } else if (Math.random() < 0.05) {
                // Occasionally fails completely
                reject(new Error("User activity service temporarily unavailable"));
            } else {
                resolve({
                    events: generateRandomEvents(Math.floor(Math.random() * 20) + 10),
                    complete: true
                });
            }
        }, delay);
    });
function generateRandomEvents(count) {
    const events = [];
    const eventTypes = ["page_view", "click", "scroll", "search"];

    for (let i = 0; i < count; i++) {
        events.push({
            eventId: `evt-${Math.random().toString(36).substring(2, 10)}`,
            userId: `user-${Math.floor(Math.random() * 1000)}`,
            type: eventTypes[Math.floor(Math.random() * eventTypes.length)],
            timestamp: Date.now() - Math.floor(Math.random() * 60000),
            data: {
                page: Math.random() < 0.5 ? "/products" : "/checkout",
                duration: Math.floor(Math.random() * 300)
            }
        });
    }
    return events;
}

}

/**

  • Fetches recent transaction data

  • Returns financial transaction information

  • Slower but fairly reliable service

  • Data format differs from user activity */ async function fetchTransactions(timeWindow) { return new Promise((resolve, reject) => { const delay = 200 + Math.random() * 150; // 200-350ms setTimeout(() => { if (Math.random() < 0.08) { reject(new Error("Transaction service unavailable")); } else { const transactions = []; const count = Math.floor(Math.random() * 8) + 2;

             for (let i = 0; i < count; i++) {
                 transactions.push({
                     transactionId: `tx-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`,
                     customerData: {
                         id: `user-${Math.floor(Math.random() * 1000)}`,
                     },
                     amount: +(Math.random() * 200 + 10).toFixed(2),
                     status: Math.random() < 0.9 ? "completed" : "pending",
                     items: Math.floor(Math.random() * 5) + 1,
                     createdAt: new Date(Date.now() - Math.floor(Math.random() * 3600000)).toISOString()
                 });
             }
             resolve({
                 data: transactions,
                 meta: {
                     total: transactions.length,
                     timeframe: timeWindow
                 }
             });
         }
     }, delay);
    

    }); }

/**

  • Fetches inventory status updates

  • Returns stock level alerts and changes

  • Fast but returns data in a completely different structure */ async function fetchInventoryAlerts(urgencyLevel = "all") { return new Promise((resolve, reject) => { const delay = 50 + Math.random() * 100; // 50-150ms setTimeout(() => { if (Math.random() < 0.1) { reject(new Error("Inventory service connection refused")); } else { // Returns XML-like string that needs parsing const alertCount = Math.floor(Math.random() * 5); let xmlResponse = &lt;inventory-alerts generated="${new Date().toISOString()}" count="${alertCount}"&gt;;

             for (let i = 0; i < alertCount; i++) {
                 const productId = Math.floor(Math.random() * 1000);
                 const currentStock = Math.floor(Math.random() * 10);
                 const threshold = 5;
                 const urgency = currentStock < 2 ? "high" : "medium";
    
                 if (urgencyLevel === "all" || urgencyLevel === urgency) {
                     xmlResponse += `
             &lt;alert type="stock" urgency="${urgency}"&gt;
               &lt;product id="${productId}" /&gt;
               &lt;current-stock&gt;${currentStock}&lt;/current-stock&gt;
               &lt;threshold&gt;${threshold}&lt;/threshold&gt;
               &lt;location warehouse="${Math.floor(Math.random() * 5) + 1}" /&gt;
             &lt;/alert&gt;
           `;
                 }
             }
    
             xmlResponse += '&lt;/inventory-alerts&gt;';
             resolve(xmlResponse);
         }
     }, delay);
    

    }); }

Task:

Create an Express.js API endpoint that:

  • Implements a GET /api/dashboard endpoint that accepts a timeWindow query parameter (e.g., "15m", "1h", "24h")
  • Fetches data from all three services concurrently
  • Handles the different response formats, error scenarios, and partial data
  • Aggregates the data into a unified dashboard response
  • Implements proper timeout handling (maximum 300ms total response time)

Sample response :

{
"dashboard": {
"timeWindow": "15m", // or whatever was requested
"summary": {
"activeUsers": 42,
"revenueTotal": 1234.56,
"conversionRate": 2.7,
"inventoryAlerts": 3
},
"details": {
"userActivity": {
// Processed user activity data
"complete": true|false,
// Additional details
},
"transactions": {
// Processed transaction data
// Additional details
},
"inventory": {
// Processed inventory data
// Additional details
}
},
"status": {
"servicesResponded": ["userActivity", "transactions", "inventory"],
"servicesFailed": [],
"responseTime": 298
}
}
}

Interview Questions (1)

Q1
Real-time Analytics Dashboard Backend Service
System DesignHard

You're developing a backend service for a real-time analytics dashboard that tracks user interactions across an e-commerce platform. Your service needs to:

  • Process incoming event data from multiple sources
  • Perform aggregation operations
  • Provide API endpoints for the frontend dashboard with summarized data (given below)

Core Requirements:

Your service needs to fetch and combine data from three internal data sources:

  • User Activity Service: Tracks page views, clicks, and session data
  • Transaction Service: Tracks purchases, cart additions, and checkout events
  • Inventory Service: Tracks product stock levels and alerts

Note that each service has different response characteristics, data formats, and reliability issues:

/**
 * Fetches recent user activity data
 * Returns detailed user interaction events
 * High volume data, occasionally returns partial results
 * ~150ms average response time with occasional spikes
 */
async function fetchUserActivity(timeWindow) {
    return new Promise((resolve, reject) => {
        const delay = 100 + Math.random() * 100; // 100-200ms
        setTimeout(() => {
            if (Math.random() < 0.15) {
                // Sometimes returns partial data with an error flag
                resolve({
                    events: generateRandomEvents(Math.floor(Math.random() * 10) + 5),
                    complete: false,
                    error: "Service degraded, partial data returned"
                });
            } else if (Math.random() < 0.05) {
                // Occasionally fails completely
                reject(new Error("User activity service temporarily unavailable"));
            } else {
                resolve({
                    events: generateRandomEvents(Math.floor(Math.random() * 20) + 10),
                    complete: true
                });
            }
        }, delay);
    });
function generateRandomEvents(count) {
    const events = [];
    const eventTypes = ["page_view", "click", "scroll", "search"];

    for (let i = 0; i < count; i++) {
        events.push({
            eventId: `evt-${Math.random().toString(36).substring(2, 10)}`,
            userId: `user-${Math.floor(Math.random() * 1000)}`,
            type: eventTypes[Math.floor(Math.random() * eventTypes.length)],
            timestamp: Date.now() - Math.floor(Math.random() * 60000),
            data: {
                page: Math.random() < 0.5 ? "/products" : "/checkout",
                duration: Math.floor(Math.random() * 300)
            }
        });
    }
    return events;
}

}

/**

  • Fetches recent transaction data

  • Returns financial transaction information

  • Slower but fairly reliable service

  • Data format differs from user activity */ async function fetchTransactions(timeWindow) { return new Promise((resolve, reject) => { const delay = 200 + Math.random() * 150; // 200-350ms setTimeout(() => { if (Math.random() < 0.08) { reject(new Error("Transaction service unavailable")); } else { const transactions = []; const count = Math.floor(Math.random() * 8) + 2;

             for (let i = 0; i < count; i++) {
                 transactions.push({
                     transactionId: `tx-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`,
                     customerData: {
                         id: `user-${Math.floor(Math.random() * 1000)}`,
                     },
                     amount: +(Math.random() * 200 + 10).toFixed(2),
                     status: Math.random() < 0.9 ? "completed" : "pending",
                     items: Math.floor(Math.random() * 5) + 1,
                     createdAt: new Date(Date.now() - Math.floor(Math.random() * 3600000)).toISOString()
                 });
             }
             resolve({
                 data: transactions,
                 meta: {
                     total: transactions.length,
                     timeframe: timeWindow
                 }
             });
         }
     }, delay);
    

    }); }

/**

  • Fetches inventory status updates

  • Returns stock level alerts and changes

  • Fast but returns data in a completely different structure */ async function fetchInventoryAlerts(urgencyLevel = "all") { return new Promise((resolve, reject) => { const delay = 50 + Math.random() * 100; // 50-150ms setTimeout(() => { if (Math.random() < 0.1) { reject(new Error("Inventory service connection refused")); } else { // Returns XML-like string that needs parsing const alertCount = Math.floor(Math.random() * 5); let xmlResponse = &lt;inventory-alerts generated="${new Date().toISOString()}" count="${alertCount}"&gt;;

             for (let i = 0; i < alertCount; i++) {
                 const productId = Math.floor(Math.random() * 1000);
                 const currentStock = Math.floor(Math.random() * 10);
                 const threshold = 5;
                 const urgency = currentStock < 2 ? "high" : "medium";
    
                 if (urgencyLevel === "all" || urgencyLevel === urgency) {
                     xmlResponse += `
             &lt;alert type="stock" urgency="${urgency}"&gt;
               &lt;product id="${productId}" /&gt;
               &lt;current-stock&gt;${currentStock}&lt;/current-stock&gt;
               &lt;threshold&gt;${threshold}&lt;/threshold&gt;
               &lt;location warehouse="${Math.floor(Math.random() * 5) + 1}" /&gt;
             &lt;/alert&gt;
           `;
                 }
             }
    
             xmlResponse += '&lt;/inventory-alerts&gt;';
             resolve(xmlResponse);
         }
     }, delay);
    

    }); }

Task:

Create an Express.js API endpoint that:

  • Implements a GET /api/dashboard endpoint that accepts a timeWindow query parameter (e.g., "15m", "1h", "24h")
  • Fetches data from all three services concurrently
  • Handles the different response formats, error scenarios, and partial data
  • Aggregates the data into a unified dashboard response
  • Implements proper timeout handling (maximum 300ms total response time)

Sample response :

{
"dashboard": {
"timeWindow": "15m", // or whatever was requested
"summary": {
"activeUsers": 42,
"revenueTotal": 1234.56,
"conversionRate": 2.7,
"inventoryAlerts": 3
},
"details": {
"userActivity": {
// Processed user activity data
"complete": true|false,
// Additional details
},
"transactions": {
// Processed transaction data
// Additional details
},
"inventory": {
// Processed inventory data
// Additional details
}
},
"status": {
"servicesResponded": ["userActivity", "transactions", "inventory"],
"servicesFailed": [],
"responseTime": 298
}
}
}

Discussion (0)

Share your thoughts and ask questions

Join the Discussion

Sign in with Google to share your thoughts and ask questions

No comments yet

Be the first to share your thoughts and start the discussion!