
Real Zepto Interview Experience
3 Rounds: JavaScript Deep Dive, Machine Coding in React & Hiring Manager
Round 1: JavaScript Deep Dive (60 mins)
Started with a project deep dive, then moved into heavy JavaScript fundamentals. The interviewer wants to see that you truly understand the language — not just surface-level syntax but closures, prototypes, async patterns, and data manipulation.
1️⃣ Implement Infinite Curry: sum(1)(2)(3)() returns 6
Difficulty: Medium | Time: 10 minutes
Create a function
sum that can be called with any number of chained invocations. When called with no arguments (or empty parentheses), it returns the total sum of all previous arguments.• sum(5)(10)() → 15
• sum(1)(2)(3)(4)(5)() → 15
function sum(a) {
return function inner(b) {
if (b === undefined) {
return a;
}
return sum(a + b);
};
}
// How it works:
// sum(1) returns inner, which has closure over a=1
// inner(2) calls sum(1+2) = sum(3), returns new inner with a=3
// inner(3) calls sum(3+3) = sum(6), returns new inner with a=6
// inner() — b is undefined, returns a = 6
console.log(sum(1)(2)(3)()); // 6
console.log(sum(5)(10)()); // 15
console.log(sum(1)(2)(3)(4)(5)()); // 15Key Concepts:✓ Closures — each invocation captures the running total
✓ Recursion — sum calls itself with accumulated value
✓ Function as return value — enables chaining
2️⃣ Generic Deep Compare Function
Difficulty: Medium-Hard | Time: 15 minutes
Implement a function that deeply compares any two values — primitives, objects, arrays, nested structures, and mixed types. Return
true if they are structurally equal.🎯 Solution:function deepEqual(a, b) {
// 1. Strict equality (handles primitives, null, undefined, same ref)
if (a === b) return true;
// 2. If either is null/undefined or not an object, they're not equal
if (a == null || b == null) return false;
if (typeof a !== 'object' || typeof b !== 'object') return false;
// 3. Handle Date objects
if (a instanceof Date && b instanceof Date) {
return a.getTime() === b.getTime();
}
// 4. Handle RegExp
if (a instanceof RegExp && b instanceof RegExp) {
return a.toString() === b.toString();
}
// 5. Handle Arrays
if (Array.isArray(a) !== Array.isArray(b)) return false;
// 6. Compare keys
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
// 7. Recursively compare each key
for (const key of keysA) {
if (!keysB.includes(key)) return false;
if (!deepEqual(a[key], b[key])) return false;
}
return true;
}
// Tests
deepEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } }); // true
deepEqual([1, [2, 3]], [1, [2, 3]]); // true
deepEqual({ a: 1 }, { a: 1, b: 2 }); // false
deepEqual(new Date('2024-01-01'), new Date('2024-01-01')); // trueWhy Zepto Asks This:✓ Tests recursive thinking
✓ Edge case handling (Date, RegExp, null, arrays vs objects)
✓ Real-world use: State comparison, test assertions, memoization
3️⃣ Polyfill: Promise.all & Promise.allSettled
Difficulty: Medium | Time: 15 minutes
Write polyfills for both
Promise.all and Promise.allSettled from scratch.🎯 Promise.all Polyfill:function promiseAll(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('Input must be an array'));
}
const results = [];
let completed = 0;
const total = promises.length;
if (total === 0) return resolve([]);
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((value) => {
results[index] = value; // Maintain order
completed++;
if (completed === total) {
resolve(results);
}
})
.catch(reject); // First rejection rejects the whole thing
});
});
}
// Test
promiseAll([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3)
]).then(console.log); // [1, 2, 3]🎯 Promise.allSettled Polyfill:function promiseAllSettled(promises) {
return new Promise((resolve) => {
if (!Array.isArray(promises)) {
return resolve([]);
}
const results = [];
let completed = 0;
const total = promises.length;
if (total === 0) return resolve([]);
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((value) => {
results[index] = { status: 'fulfilled', value };
})
.catch((reason) => {
results[index] = { status: 'rejected', reason };
})
.finally(() => {
completed++;
if (completed === total) {
resolve(results);
}
});
});
});
}
// Test
promiseAllSettled([
Promise.resolve(1),
Promise.reject('error'),
Promise.resolve(3)
]).then(console.log);
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: 'error' },
// { status: 'fulfilled', value: 3 }
// ]Key Difference:•
Promise.all — short-circuits on first rejection•
Promise.allSettled — waits for all, never rejects4️⃣ Execution Order: setTimeout, Promise, async/await
Difficulty: Medium | Time: 10 minutes
async function foo() {
console.log('1');
await Promise.resolve();
console.log('2');
}
console.log('3');
setTimeout(() => console.log('4'), 0);
foo();
new Promise((resolve) => {
console.log('5');
resolve();
}).then(() => console.log('6'));
console.log('7');Output: 3, 1, 5, 7, 2, 6, 4Step-by-step breakdown:
console.log('3') — synchronous, runs first2.
setTimeout — scheduled as macrotask3.
foo() called → console.log('1') — synchronous4.
await pauses foo, rest of foo goes to microtask queue5. Promise executor runs synchronously →
console.log('5')6.
.then callback queued as microtask7.
console.log('7') — synchronous8. Call stack empty → microtasks:
'2' (from await), then '6' (from .then)9. Macrotask:
'4' (setTimeout)5️⃣ Closures: Implement once() and memoize() with Cache Limit
Difficulty: Medium | Time: 10 minutes
function once(fn) {
let called = false;
let result;
return function (...args) {
if (!called) {
called = true;
result = fn.apply(this, args);
}
return result;
};
}
// Usage
const initialize = once(() => {
console.log('Initializing...');
return 'done';
});
initialize(); // logs "Initializing...", returns "done"
initialize(); // returns "done" (no log)
initialize(); // returns "done" (no log)memoize() with LRU cache limit:function memoize(fn, limit = 10) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
// Move to end (most recently used)
const value = cache.get(key);
cache.delete(key);
cache.set(key, value);
return value;
}
const result = fn.apply(this, args);
// Evict oldest if over limit
if (cache.size >= limit) {
const firstKey = cache.keys().next().value;
cache.delete(firstKey);
}
cache.set(key, result);
return result;
};
}
// Usage
const expensiveCalc = memoize((n) => {
console.log('Computing...');
return n * n;
}, 3); // Cache limit of 3
expensiveCalc(2); // Computing... → 4
expensiveCalc(3); // Computing... → 9
expensiveCalc(2); // 4 (cached, no computation)
expensiveCalc(4); // Computing... → 16
expensiveCalc(5); // Computing... → 25 (evicts oldest)Why This Matters:✓ once() — prevents duplicate API calls, initialization logic
✓ memoize() — performance optimization for expensive computations
✓ LRU eviction — memory management in real applications
6️⃣ Flatten Deeply Nested Object with Circular Reference Handling
Difficulty: Hard | Time: 15 minutes
Flatten a deeply nested object into a single-level object with dot-notation keys. Handle circular references without infinite loops.
Output: { "a.b.c": 1, "d.0": 2, "d.1": 3 }
function flattenObject(obj, prefix = '', seen = new WeakSet()) {
const result = {};
// Circular reference check
if (typeof obj === 'object' && obj !== null) {
if (seen.has(obj)) {
return { [prefix]: '[Circular]' };
}
seen.add(obj);
}
for (const key in obj) {
if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
const newKey = prefix ? `${prefix}.${key}` : key;
const value = obj[key];
if (value !== null && typeof value === 'object' && !(value instanceof Date)) {
Object.assign(result, flattenObject(value, newKey, seen));
} else {
result[newKey] = value;
}
}
return result;
}
// Test
const obj = { a: { b: { c: 1 } }, d: [2, 3], e: { f: null } };
console.log(flattenObject(obj));
// { "a.b.c": 1, "d.0": 2, "d.1": 3, "e.f": null }
// Circular reference test
const circular = { name: 'test' };
circular.self = circular;
console.log(flattenObject(circular));
// { "name": "test", "self": "[Circular]" }Key Points:✓ WeakSet tracks visited objects (allows GC, no memory leak)
✓ Handles arrays (indexed as dot notation)
✓ Handles null, Date, and other edge cases
7️⃣ Prototypes: __proto__ vs prototype vs Object.create
Difficulty: Medium | Time: Conceptual
__proto__, prototype, and Object.create().// 1. prototype — property on FUNCTIONS (constructor functions)
// It's the blueprint for instances created with 'new'
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return this.name + ' makes a sound';
};
// 2. __proto__ — property on OBJECTS (instances)
// Points to the prototype of the constructor that created it
const dog = new Animal('Rex');
console.log(dog.__proto__ === Animal.prototype); // true
console.log(dog.__proto__.__proto__ === Object.prototype); // true
// 3. Object.create() — creates object with specified prototype
// No constructor function needed
const proto = {
greet() { return 'Hello, ' + this.name; }
};
const person = Object.create(proto);
person.name = 'Alice';
console.log(person.greet()); // "Hello, Alice"
console.log(person.__proto__ === proto); // true
// KEY DIFFERENCES:
// prototype → lives on functions, shared by all instances
// __proto__ → lives on objects, points UP the chain (deprecated, use Object.getPrototypeOf)
// Object.create → creates new object with a given prototype, cleaner than newdog.__proto__ → Animal.prototype → Object.prototype → null. Interviewers love visual explanations.💡 Interview Tips for Round 1:
- Think out loud: Explain your approach before writing code
- Start with edge cases: Mention them upfront — shows senior thinking
- Know the "why":Not just how closures work, but when you'd use them in production
- Be ready for follow-ups: Every answer leads to a deeper question
- Practice writing from scratch: No autocomplete in interviews
Zepto's Round 1 is pure JavaScript mastery. If you can write these from scratch without hesitation, you're ready. Master JavaScript fundamentals with structured practice →
Round 2: Machine Coding in React (90 mins)
Real product scenarios with multiple follow-ups. You build a component, then they keep adding requirements. The goal is to see how you structure code under evolving requirements — extensibility matters.
📌 Build a Toast Notification System
Difficulty: Hard | Time: 90 minutes (progressive requirements)
• Auto-dismiss after configurable duration
• Queue system — max 3 visible at a time
• Custom positioning (top-right, bottom-left, etc.)
• Smooth enter/exit animations
// toast-context.jsx
import { createContext, useContext, useReducer, useCallback, useRef } from 'react';
const ToastContext = createContext(null);
const TOAST_LIMIT = 3;
const DEFAULT_DURATION = 3000;
function toastReducer(state, action) {
switch (action.type) {
case 'ADD':
return {
...state,
toasts: [...state.toasts, action.payload],
queue: state.toasts.length >= TOAST_LIMIT
? [...state.queue, action.payload]
: state.queue,
};
case 'REMOVE':
const remaining = state.toasts.filter(t => t.id !== action.id);
// Pull from queue if available
if (state.queue.length > 0 && remaining.length < TOAST_LIMIT) {
const [next, ...restQueue] = state.queue;
return { toasts: [...remaining, next], queue: restQueue };
}
return { ...state, toasts: remaining };
default:
return state;
}
}
export function ToastProvider({ children, position = 'top-right' }) {
const [state, dispatch] = useReducer(toastReducer, { toasts: [], queue: [] });
const toastId = useRef(0);
const addToast = useCallback(({ message, variant = 'info', duration = DEFAULT_DURATION }) => {
const id = ++toastId.current;
dispatch({ type: 'ADD', payload: { id, message, variant, duration } });
if (duration > 0) {
setTimeout(() => {
dispatch({ type: 'REMOVE', id });
}, duration);
}
return id;
}, []);
const removeToast = useCallback((id) => {
dispatch({ type: 'REMOVE', id });
}, []);
return (
<ToastContext.Provider value={{ addToast, removeToast }}>
{children}
<ToastContainer toasts={state.toasts} position={position} onDismiss={removeToast} />
</ToastContext.Provider>
);
}
export const useToast = () => useContext(ToastContext);Follow-up 1: No re-rendering the entire app on every toast// Solution: Move toast state OUTSIDE React using a store pattern
// Only the ToastContainer subscribes to changes
let listeners = [];
let toasts = [];
const toastStore = {
getState: () => toasts,
subscribe: (listener) => {
listeners.push(listener);
return () => { listeners = listeners.filter(l => l !== listener); };
},
addToast: (toast) => {
toasts = [...toasts, { ...toast, id: Date.now() }];
listeners.forEach(l => l());
},
removeToast: (id) => {
toasts = toasts.filter(t => t.id !== id);
listeners.forEach(l => l());
}
};
// Only ToastContainer subscribes — rest of app never re-renders
function ToastContainer() {
const [state, setState] = useState(toastStore.getState());
useEffect(() => {
return toastStore.subscribe(() => setState(toastStore.getState()));
}, []);
return (
<div className="toast-container">
{state.slice(0, TOAST_LIMIT).map(toast => (
<Toast key={toast.id} {...toast} onDismiss={() => toastStore.removeToast(toast.id)} />
))}
</div>
);
}
// Any component can trigger toasts without prop drilling or context re-renders
// import { toastStore } from './toast-store';
// toastStore.addToast({ message: 'Saved!', variant: 'success' });Follow-up 2: Programmatic dismiss via refimport { useRef, useImperativeHandle, forwardRef } from 'react';
// Expose dismiss method via ref
const ToastManager = forwardRef((props, ref) => {
const [toasts, setToasts] = useState([]);
useImperativeHandle(ref, () => ({
dismiss: (id) => {
setToasts(prev => prev.filter(t => t.id !== id));
},
dismissAll: () => {
setToasts([]);
},
add: (toast) => {
const id = Date.now();
setToasts(prev => [...prev, { ...toast, id }]);
return id; // Return id for programmatic dismiss
}
}));
return (
<div className="toast-container">
{toasts.map(t => <Toast key={t.id} {...t} />)}
</div>
);
});
// Usage in parent
function App() {
const toastRef = useRef();
const handleSave = async () => {
const loadingId = toastRef.current.add({
message: 'Saving...',
variant: 'info',
duration: 0 // persistent
});
await saveData();
// Programmatically dismiss loading toast
toastRef.current.dismiss(loadingId);
toastRef.current.add({ message: 'Saved!', variant: 'success' });
};
return (
<>
<button onClick={handleSave}>Save</button>
<ToastManager ref={toastRef} />
</>
);
}• Component architecture — can you separate concerns cleanly?
• Performance awareness — do you think about re-renders?
• API design — is your component easy for other devs to use?
• Adaptability — can you extend without rewriting?
📌 Variant: Build an OTP Input Component
Difficulty: Medium | Time: 30 minutes
• Auto-focus next box on input
• Backspace moves to previous box
• Paste support — distributes digits across boxes
• onComplete callback when all boxes filled
import { useState, useRef, useCallback } from 'react';
function OTPInput({ length = 6, onComplete }) {
const [otp, setOtp] = useState(new Array(length).fill(''));
const inputRefs = useRef([]);
const handleChange = useCallback((e, index) => {
const value = e.target.value;
if (!/^\d*$/.test(value)) return; // Only digits
const newOtp = [...otp];
// Take only last character (handles overwrite)
newOtp[index] = value.slice(-1);
setOtp(newOtp);
// Auto-focus next input
if (value && index < length - 1) {
inputRefs.current[index + 1].focus();
}
// Check if complete
const otpString = newOtp.join('');
if (otpString.length === length && onComplete) {
onComplete(otpString);
}
}, [otp, length, onComplete]);
const handleKeyDown = useCallback((e, index) => {
if (e.key === 'Backspace') {
if (!otp[index] && index > 0) {
// If current box empty, move to previous
inputRefs.current[index - 1].focus();
const newOtp = [...otp];
newOtp[index - 1] = '';
setOtp(newOtp);
}
}
}, [otp]);
const handlePaste = useCallback((e) => {
e.preventDefault();
const pastedData = e.clipboardData.getData('text').slice(0, length);
if (!/^\d+$/.test(pastedData)) return;
const newOtp = [...otp];
pastedData.split('').forEach((digit, i) => {
newOtp[i] = digit;
});
setOtp(newOtp);
// Focus last filled or next empty
const focusIndex = Math.min(pastedData.length, length - 1);
inputRefs.current[focusIndex].focus();
if (pastedData.length === length && onComplete) {
onComplete(pastedData);
}
}, [otp, length, onComplete]);
return (
<div style={{ display: 'flex', gap: '8px' }}>
{otp.map((digit, index) => (
<input
key={index}
ref={(el) => (inputRefs.current[index] = el)}
type="text"
inputMode="numeric"
maxLength={1}
value={digit}
onChange={(e) => handleChange(e, index)}
onKeyDown={(e) => handleKeyDown(e, index)}
onPaste={handlePaste}
style={{
width: '48px', height: '48px',
textAlign: 'center', fontSize: '1.5rem',
border: '2px solid #ddd', borderRadius: '8px'
}}
/>
))}
</div>
);
}📌 Variant: Type Ahead Search with Debounce & Keyboard Navigation
Difficulty: Medium-Hard | Time: 40 minutes
• Cancel previous pending requests
• Keyboard navigation (↑/↓ to navigate, Enter to select)
• Loading and empty states
• Click outside to close dropdown
import { useState, useEffect, useRef, useCallback } from 'react';
function useDebounce(value, delay) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounced;
}
function TypeAheadSearch({ fetchSuggestions, onSelect }) {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(false);
const [activeIndex, setActiveIndex] = useState(-1);
const [isOpen, setIsOpen] = useState(false);
const abortRef = useRef(null);
const containerRef = useRef(null);
const debouncedQuery = useDebounce(query, 300);
// Fetch results
useEffect(() => {
if (!debouncedQuery.trim()) {
setResults([]);
setIsOpen(false);
return;
}
// Cancel previous request
if (abortRef.current) abortRef.current.abort();
abortRef.current = new AbortController();
setLoading(true);
fetchSuggestions(debouncedQuery, abortRef.current.signal)
.then((data) => {
setResults(data);
setIsOpen(true);
setActiveIndex(-1);
})
.catch((err) => {
if (err.name !== 'AbortError') setResults([]);
})
.finally(() => setLoading(false));
}, [debouncedQuery, fetchSuggestions]);
// Click outside to close
useEffect(() => {
const handleClick = (e) => {
if (containerRef.current && !containerRef.current.contains(e.target)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClick);
return () => document.removeEventListener('mousedown', handleClick);
}, []);
const handleKeyDown = useCallback((e) => {
if (!isOpen) return;
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
setActiveIndex(prev => Math.min(prev + 1, results.length - 1));
break;
case 'ArrowUp':
e.preventDefault();
setActiveIndex(prev => Math.max(prev - 1, 0));
break;
case 'Enter':
if (activeIndex >= 0) {
onSelect(results[activeIndex]);
setIsOpen(false);
setQuery(results[activeIndex].label);
}
break;
case 'Escape':
setIsOpen(false);
break;
}
}, [isOpen, results, activeIndex, onSelect]);
return (
<div ref={containerRef} style={{ position: 'relative' }}>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={handleKeyDown}
onFocus={() => results.length > 0 && setIsOpen(true)}
placeholder="Search..."
/>
{loading && <span className="spinner" />}
{isOpen && results.length > 0 && (
<ul className="dropdown">
{results.map((item, i) => (
<li
key={item.id}
className={i === activeIndex ? 'active' : ''}
onClick={() => { onSelect(item); setIsOpen(false); setQuery(item.label); }}
onMouseEnter={() => setActiveIndex(i)}
>
{item.label}
</li>
))}
</ul>
)}
</div>
);
}💡 Machine Coding Tips for Zepto:
- Start with the simplest working version: Get it rendering first, then add features
- Think about extensibility: They WILL add follow-ups, so keep code flexible
- Separate logic from UI: Custom hooks, stores, or reducers — show you can decouple
- Handle edge cases: Rapid clicks, network failures, race conditions
- Name things well: Clean variable names show you write production code
Machine coding at Zepto is all about building real product features under time pressure. Practice building 2-3 components end-to-end every week. Get weekly machine coding practice with expert feedback →
Round 3: Hiring Manager (45 mins)
Project deep dive + behavioral + product thinking. This is not a soft round — they're evaluating your depth of ownership, communication clarity, and whether you'd fit the team culture at Zepto.
1️⃣ Walk me through your most challenging frontend project
• "You said you improved performance — what exactly? How did you measure?"
• "You mentioned you led the project — what decisions did YOU make vs the team?"
• If you can't go deep, they assume you weren't the real owner
Situation: What was the product/feature, team size, your role
Task: What was the specific technical challenge
Action: What YOU did — architecture decisions, trade-offs considered, alternatives rejected
Result: Quantifiable outcome (metrics improved, bugs reduced, time saved)
+ Reflection: What would you do differently now?
2️⃣ A real performance bug you fixed in production
2. Diagnosis: Root cause analysis — what tools did you use? (DevTools, Lighthouse, React Profiler)
3. Fix: What was the solution? Why that approach over alternatives?
4. Validation: How did you verify it was fixed? What metrics improved?
5. Prevention: What did you put in place to prevent it happening again?
• Fixed a re-render cascade that caused 500ms lag on every keystroke
• Identified a memory leak from event listeners in a SPA
• Reduced bundle size by 40% by analyzing and splitting chunks
• Fixed a hydration mismatch causing layout shift in production
3️⃣ Tell me about a time you disagreed with product or design
✓ You can push back with data, not ego
✓ You understand business impact of technical decisions
✓ You can collaborate even when disagreeing
✓ You know when to commit vs when to escalate
Best: Show a nuanced situation where you backed your position with evidence, listened to the other side, and the team reached a better outcome.
4️⃣ What would you change about the Zepto app right now?
• Open the Zepto app and use it for 15 minutes
• Place a real order (or go through the flow)
• Note: loading states, transitions, error handling, search UX
• Check performance on a slow network (3G throttle)
• Compare with Blinkit/Swiggy Instamart
"I noticed [specific issue] when [specific action]. This impacts [user metric]. I'd improve it by [technical approach] because [reasoning]. The expected impact would be [measurable outcome]."
Good areas to observe:
• Search experience (autocomplete speed, relevance)
• Category browsing (virtualization, image loading)
• Cart management (optimistic updates, sync)
• Delivery tracking (real-time updates, map performance)
• Skeleton loading and perceived performance
5️⃣ Why Zepto over other quick commerce companies?
2. Scale: What technical challenges at Zepto interest you? (Real-time, performance at scale)
3. Growth: Why is this the right time to join? (Stage of company, engineering culture)
4. Team: What have you heard about the engineering team? (Blog posts, talks, tech stack choices)
💡 Hiring Manager Round Tips:
- Use the app before this round: 15 minutes minimum. Have one specific improvement ready with a technical approach.
- Prepare 2-3 deep projects: Ones where you can explain architecture decisions, trade-offs, and outcomes at 3 levels of depth.
- Quantify everything:"Improved performance" means nothing. "Reduced LCP from 3.2s to 1.1s" means everything.
- Show ownership:"I decided" > "We decided" > "The team decided"
- Be honest:If you don't know something, say so. Faking it at this level is obvious.
What Actually Helps at Zepto Interviews
They dig deep into fundamentals. Surface-level knowledge won't survive the follow-ups.
No IDE help in interviews. You should be able to write curry, memoize, debounce, Promise polyfills on a whiteboard.
90 minutes goes fast with follow-ups. Practice building toast, OTP, autocomplete in 30 minutes each.
Silent coding is a red flag. Narrate your thinking — trade-offs, alternatives considered, edge cases.
Architecture decisions, performance fixes, technical trade-offs. They will ask 3 levels of "why".
15 minutes of real usage. Note one improvement you'd make with a specific technical approach.
Ready to Crack Your Zepto Interview?
Join our cohort and get structured preparation with 1-on-1 guidance from a Staff Engineer who has mentored 100+ developers.
