Astro Q-Commerce — System Design Case Interview
11 case study system design untuk Astro (q-commerce / instant grocery) + bonus perbandingan Klik Indomaret: inventory real-time per dark store, geo-serviceability, driver dispatch, ETA estimation, checkout saga, promo engine, search catalog, real-time tracking, demand forecasting, dan dynamic pricing. Mencakup implementasi Go, trade-off, edge case, dan potential follow-up question per case.
Model bisnis: q-commerce / instant grocery — dark store (micro-fulfillment center), antar dalam hitungan menit, 15k+ SKU, 24 jam, armada driver sendiri ("Astronauts"), area Jabodetabek, bayar e-wallet/VA (no COD).
Case interview muncul dari constraint khas q-commerce: inventory real-time per hub, dispatch driver, geo-serviceability, dan latency rendah. Flash sale cuma satu dari banyak.
Bonus: Klik Indomaret — Q-Commerce dari Raksasa Ritel
Selain Astro, pemain q-commerce besar di Indonesia: Klik Indomaret dari Indomaret Group. Ini adalah platform instant delivery yang memanfaatkan 20k+ toko Indomaret existing sebagai fulfillment point — bukan dark store dedicated seperti Astro. Klik Indomaret punya armada driver sendiri (tidak bergantung Gojek/Grab), menjadikannya perbandingan menarik dari sisi driver dispatch & fleet management.
Perbandingan Model: Astro vs Klik Indomaret
| Dimensi | Astro | Klik Indomaret |
|---|---|---|
| Fulfillment | Dark store (micro-fulfillment) dedicated | 20k+ toko Indomaret existing |
| Delivery | Driver sendiri ("Astronauts") | Driver sendiri (fleet internal) |
| SKU | 15k+ (grocery focused) | Produk Indomaret (snack, sembako, obat) |
| Coverage | Jabodetabek (terbatas) | Nasional (seluruh toko Indomaret) |
| Inventory | Real-time per dark store, stok dalam | Real-time per toko kecil, stok terbatas |
| ETA Target | 15-30 menit | 1-2 jam (jarak toko→rumah) |
| Model Bisnis | Instant grocery, margin dari efisiensi | Omnichannel retail, margin dari skala |
Aspek Teknis yang Membedakan
Store Selection — Cari Toko Terdekat dengan Stok:
Berbeda dengan Astro yang pre-assign user ke hub via polygon, Klik Indomaret harus dynamic store selection tiap kali user order: cari toko terdekat yang punya SEMUA item di cart. Ini lebih kompleks karena stok per toko kecil dan volatile.
// StoreSelector: cari toko terdekat dengan semua item tersedia (Go)
package klikindomaret
import (
"context"
"fmt"
"sort"
"github.com/redis/go-redis/v9"
"github.com/uber/h3-go/v4"
)
type Store struct {
ID string
Lat, Lng float64
H3Cell string // H3 resolution 9 (~174m²)
}
type StoreSelector struct {
rdb *redis.ClusterClient
stores []Store // preload via GeoIndex
}
// FindBestStore: cari toko terdekat yang punya SEMUA item dengan qty cukup.
// Strategi: iterasi toko dari terdekat, check stok via Redis pipeline.
func (ss *StoreSelector) FindBestStore(
ctx context.Context,
userLat, userLng float64,
items []CartItem,
) (*Store, error) {
// 1. Sort toko by haversine distance
candidates := ss.nearestStores(userLat, userLng)
// 2. Cek stok di tiap toko dengan Redis pipeline (batch check)
for _, store := range candidates {
if ss.hasAllStock(ctx, store.ID, items) {
return &store, nil
}
}
return nil, fmt.Errorf("no store with all items in stock")
}
func (ss *StoreSelector) hasAllStock(
ctx context.Context, storeID string, items []CartItem,
) bool {
pipe := ss.rdb.Pipeline()
cmds := make([]*redis.StringCmd, len(items))
for i, item := range items {
key := fmt.Sprintf("stock:%s:%s", storeID, item.SkuID)
cmds[i] = pipe.Get(ctx, key)
}
if _, err := pipe.Exec(ctx); err != nil {
return false
}
for i, cmd := range cmds {
stock, err := cmd.Int64()
if err != nil || stock < int64(items[i].Qty) {
return false
}
}
return true
}
func (ss *StoreSelector) nearestStores(lat, lng float64) []Store {
sort.Slice(ss.stores, func(i, j int) bool {
return haversine(lat, lng, ss.stores[i].Lat, ss.stores[i].Lng) <
haversine(lat, lng, ss.stores[j].Lat, ss.stores[j].Lng)
})
// Return top 5 terdekat (stok per toko kecil, mungkin perlu beberapa percobaan)
if len(ss.stores) > 5 {
return ss.stores[:5]
}
return ss.stores
}Driver Dispatch — Fleet Management Skala Nasional:
Tantangan beda dengan Astro: driver Klik Indomaret tersebar di 20k+ toko, bukan terkonsentrasi di beberapa dark store. Dispatch harus mempertimbangkan:
- Driver terdekat dengan toko yang dipilih (bukan ke user dulu — driver pickup dari toko)
- Driver bisa assigned ke toko berbeda tiap order
- Utilisasi driver di area dengan kepadatan order rendah
// DriverDispatcher: match driver ke toko + order (Go)
type DriverDispatcher struct {
rdb *redis.ClusterClient
geoIndex *GeoIndex // driver locations via GEOSEARCH
}
var dispatchScript = redis.NewScript(`
-- Atomic claim: cek driver masih idle + assign order
local driverKey = KEYS[1] -- driver:{id}
local orderKey = KEYS[2] -- order:{id}
local storeID = ARGV[1]
local orderID = ARGV[2]
local ttl = tonumber(ARGV[3]) -- accept window (30 detik)
local status = redis.call('HGET', driverKey, 'status')
if status ~= 'idle' then
return {0, 'driver_not_available'}
end
-- Atomic: claim driver + assign order
redis.call('HSET', driverKey,
'status', 'dispatched',
'order_id', orderID,
'store_id', storeID,
'dispatched_at', ARGV[4])
redis.call('EXPIRE', driverKey, ttl) -- auto-release kalau driver ga accept
redis.call('HSET', orderKey,
'status', 'driver_assigned',
'driver_id', KEYS[1],
'store_id', storeID)
return {1, 'ok'}
`)
func (dd *DriverDispatcher) Dispatch(
ctx context.Context, orderID, storeID string, storeLat, storeLng float64,
) (*Driver, error) {
// 1. Cari driver idle terdekat dari TOKO (bukan dari user)
drivers, err := dd.geoIndex.NearestIdle(ctx, storeLat, storeLng, 5)
if err != nil || len(drivers) == 0 {
return nil, fmt.Errorf("no idle driver near store")
}
// 2. Coba claim driver satu per satu
now := time.Now().Unix()
for _, d := range drivers {
res, err := dispatchScript.Run(ctx, dd.rdb,
[]string{
fmt.Sprintf("driver:%s", d.ID),
fmt.Sprintf("order:%s", orderID),
},
storeID, orderID, 30, now,
).Slice()
if err != nil {
continue
}
if res[0].(int64) == 1 {
return &d, nil
}
}
return nil, fmt.Errorf("all nearby drivers busy")
}Relevansi dengan Case Interview
Case existing bisa di-extend dengan perspektif Klik Indomaret:
- #2 Inventory: beda fundamental — Astro stok per dark store (dalam, SKU banyak), Klik Indomaret stok per toko kecil (dangkal, SKU sedikit). Risk out-of-stock jauh lebih tinggi → perlu fallback store + partial fulfillment strategy
- #3 Geo Serviceability: Astro pakai polygon (static), Klik Indomaret pakai dynamic store selection (real-time). Interviewer suka tanya: "kapan pakai static vs dynamic assignment?"
- #4 Driver Dispatch: skala nasional 20k toko vs Astro 50 dark store Jabodetabek. Fleet management完全不同 — perlu zone-based dispatch + load balancing antar kecamatan
- #10 Demand Forecasting: stok per toko kecil bikin forecast lebih noise. Butuh aggregate forecast per kecamatan → disaggregate ke toko berdasarkan historical demand proporsi
1. Flash Sale — High-Concurrency Timed Event
Masalah: event dengan waktu terbatas (misal 12:00–14:00), stok terbatas per SKU per hub, ribuan user rebutan dalam hitungan detik. Ini ujian ekstrim untuk hot key contention, rate limiting, fairness, dan anti-bot.
Inti Desain
Stock partitioning (hot key mitigation):
SKU laris (misal 100 unit telur promo) dipecah jadi N bucket (misal 10 bucket @10 unit). Key Redis: flash:stock:{event_id}:{store_id}:{sku_id}:{bucket_N}. Saat checkout, pilih bucket random — contention terdistribusi ke N key. Kalau bucket habis, coba bucket lain. Semua bucket habis → sold out.
// StockPartitioner: atomic decrement dengan bucket fallback (Go + go-redis)
package flashsale
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
var ErrSoldOut = fmt.Errorf("sold out")
// Lua script — dieksekusi atomic oleh Redis server-side
var stockDecrementScript = redis.NewScript(`
local buckets = KEYS
local qty = tonumber(ARGV[1])
for i = 1, #buckets do
local bucket = buckets[math.random(#buckets)]
local stock = redis.call('GET', bucket)
if stock and tonumber(stock) >= qty then
redis.call('DECRBY', bucket, qty)
return {1, bucket}
end
end
return {0, nil}
`)
type StockPartitioner struct {
rdb *redis.ClusterClient
}
func NewStockPartitioner(rdb *redis.ClusterClient) *StockPartitioner {
return &StockPartitioner{rdb: rdb}
}
// ReserveStock: atomic decrement dengan random bucket selection.
// Return bucket key yang berhasil, atau ErrSoldOut.
func (s *StockPartitioner) ReserveStock(
ctx context.Context,
eventID, storeID, skuID string,
totalBuckets, qty int,
) (string, error) {
keys := make([]string, totalBuckets)
for i := range totalBuckets {
keys[i] = fmt.Sprintf(
"flash:stock:%s:%s:%s:bucket_%d",
eventID, storeID, skuID, i,
)
}
res, err := stockDecrementScript.Run(ctx, s.rdb, keys, qty).Slice()
if err != nil {
return "", fmt.Errorf("redis eval: %w", err)
}
if res[0].(int64) == 0 {
return "", ErrSoldOut
}
return res[1].(string), nil
}
// FlashSaleService: orchestrator untuk checkout flash sale.
type FlashSaleService struct {
partitioner *StockPartitioner
limiter *RateLimiter // sliding window per device fingerprint
queue *VirtualQueue // waiting room (Redis sorted set)
producer kafka.Producer // outbox event
}
func (svc *FlashSaleService) Checkout(
ctx context.Context, req CheckoutRequest,
) (*CheckoutResponse, error) {
// 1. Rate limit check — device fingerprint + IP + session
if !svc.limiter.Allow(ctx, req.DeviceFingerprint) {
// Masukkan ke waiting room
pos, eta := svc.queue.Enqueue(ctx, req)
return &CheckoutResponse{
Status: "waiting",
Position: pos,
ETA: eta,
}, nil
}
// 2. Atomic stock reservation dengan bucket partitioning
bucket, err := svc.partitioner.ReserveStock(
ctx, req.EventID, req.StoreID, req.SkuID, 10, req.Qty,
)
if errors.Is(err, ErrSoldOut) {
svc.queue.KickAll(ctx, req.EventID) // notifikasi semua waiting
return &CheckoutResponse{Status: "sold_out"}, nil
}
if err != nil {
return nil, fmt.Errorf("reserve stock: %w", err)
}
// 3. Produce order event → async processing (Kafka outbox)
svc.producer.Produce(ctx, OrderEvent{
EventID: req.EventID,
Bucket: bucket,
Qty: req.Qty,
UserID: req.UserID,
})
return &CheckoutResponse{Status: "success", Bucket: bucket}, nil
}Waiting room / virtual queue:
Kalau traffic jauh di atas kapasitas (misal 50k user rebut 500 unit), pakai queue system:
- User masuk waiting room sebelum bisa akses halaman flash sale
- Queue FIFO dengan token-based admission — hanya N user yang boleh masuk checkout bersamaan
- Tampilkan posisi antrian + estimasi waktu
- Kalau stok habis, kick semua dari queue sekaligus — UX: "Maaf, flash sale sudah berakhir"
Rate limiting per user:
- Token bucket / sliding window di API gateway — cegah 1 user spam puluhan checkout request paralel
- Fingerprint: device ID + IP + session → deteksi multi-akun dari device yang sama
- Checkout hanya boleh 1x per user per flash sale event (idempotency key:
{user_id}:{event_id})
Cache strategy:
- Halaman produk flash sale di-cache di CDN dengan TTL pendek (1–5 detik) + stale-while-revalidate
- Stock count di halaman pakai Redis langsung, bukan dari cache
- Jangan cache penuh — stok "tinggal 3!" harus real-time, bukan basi 5 detik
Arsitektur
User → CDN (static+stale) → API Gateway (rate limit)
→ Flash Sale Service
→ Redis Cluster (stock reservation, EVALSHA via Go)
→ Kafka (order event, async processing)
→ Postgres (system of record, async sync)Service terpisah dari catalog/checkout reguler — isolasi blast radius. Redis cluster dedicated untuk flash sale, jangan share sama session cache atau catalog cache.
Edge & Risk
- Race antar bucket: user A dan B sama-sama lihat stok tersedia di bucket yang sama → Lua script handle atomicity, tapi UX "stok habis saat checkout" masih bisa terjadi (bucket habis pas concurrent decrement). Wajib handle graceful.
- Bot/scalper: user pakai script otomatis bypass rate limit → wajib CAPTCHA/device attestation di entry flash sale. Lebih penting dari e-commerce biasa karena margin kecil + volume.
- Cache stampede: tepat saat flash sale mulai, puluhan ribu request ke origin → solusi: pre-warm cache sebelum event mulai, jangan expire cache pas jam 12:00:00.
- Stock reconciliation: Redis crash saat flash sale → recovery dari Postgres, tapi ada window inconsistency. Bisa pakai Redis AOF persistence + sentinel.
- Phantom stock: bucket habis di Redis tapi Postgres belum di-update, lalu Redis restart → stok "muncul" lagi. Wajib source of truth yang jelas (Redis = operational, Postgres = authoritative).
Potential Follow-up
- "Kenapa stock partitioning bukan sharding by store?"
- "Gimana cara deteksi dan block scalper real-time tanpa ganggu user genuine?"
- "Apa trade-off waiting room vs langsung checkout?"
- "Bagaimana kalau Redis master mati pas flash sale lagi jalan? Gimana failover-nya?"
- "Berapa banyak bucket yang optimal? Gimana cara nentuinnya?"
- "Kenapa service terpisah? Kenapa tidak pakai checkout reguler yang sama?"
2. Real-time Inventory per Dark Store (Oversell Prevention)
Ini jantung q-commerce.
Masalah: stok fisik ada di banyak dark store. User di Tebet cuma boleh lihat & beli stok dari hub yang cover Tebet. Dua user rebutan 1 unit terakhir telur — jangan sampai dua-duanya berhasil checkout (oversell), karena fulfillment fisik bakal gagal dan itu pengalaman buruk + refund.
Inti Desain
- Stok bersifat per-hub, jadi key-nya
(store_id, sku_id), bukan global. Sharding natural by store. - Pisahkan available vs on-hand. Saat add-to-cart/checkout lakukan reservation (decrement available, TTL misal 10–15 menit), bukan langsung potong on-hand. Kalau bayar gagal/timeout, reservation balik (compensating).
- Atomicity: pakai Redis
DECRBYdengan Lua script (cek-lalu-kurang dalam satu atomic op) sebagai source of truth untuk available, lalu async sync ke Postgres sebagai system of record. Atau full Postgres denganUPDATE ... WHERE qty >= n(optimistic, andalkan row lock + affected-rows check). Trade-off: Redis cepat tapi butuh rekonsiliasi; Postgres konsisten tapi row contention tinggi di SKU laris.
Edge & Risk
- Reservation leak kalau service crash sebelum release → wajib TTL + reaper job.
- Hot key (SKU promo) → contention di satu row/key. Mitigasi: stock partitioning (pecah 100 unit jadi 10 bucket @10, decrement random bucket).
- Konsistensi stok fisik vs sistem (barang rusak, hilang, salah hitung) → butuh cycle counting & adjustment flow, sama event audit trail.
Potential Follow-up
- "Bagaimana kalau Redis mati? Gimana recovery-nya?"
- "Gimana cara reconcile Redis dan Postgres kalau ada drift?"
- "Apa trade-off reservation TTL pendek vs panjang?"
3. Serviceability & Hub Assignment (Geo)
Masalah: user buka app, sistem harus tentukan dia masuk coverage hub mana, dalam <100ms, sebelum render katalog+harga+stok.
Inti Desain
- Tiap dark store punya service polygon (bukan cuma radius, karena jalan/sungai/tol bikin radius bohong). Simpan polygon di PostGIS, query
ST_Contains(polygon, user_point). - Untuk skala & latency: precompute pakai geohash / H3 (Uber) → map cell → store. Lookup jadi O(1) by cell index di Redis, fallback ke PostGIS kalau cell di boundary.
- Kalau satu titik di-cover >1 hub → tie-break by jarak/ETA/ketersediaan stok/load hub.
Trade-off
H3 cepat tapi cell di tepi polygon bisa false positive/negative → perlu resolusi cell cukup halus + verifikasi PostGIS di edge case.
Risk
User di luar coverage harus dapat UX jelas (waitlist/daftar area), bukan katalog kosong.
Potential Follow-up
- "Kenapa H3 bukan S2? Apa bedanya?"
- "Gimana handle perubahan polygon coverage (hub baru, hub tutup)?"
- "Bagaimana kalau user di perbatasan dua hub dan stok di hub A ada, di hub B habis?"
4. Order Dispatch & Driver Allocation
Masalah: order masuk → assign ke driver yang tepat. Tujuan: minimize delivery time, maximize utilisasi driver, fair distribution.
Inti Desain
- Ini bukan FIFO sederhana, ini assignment problem. Pendekatan batching: kumpulkan order beberapa detik, jalankan matching (Hungarian/greedy) terhadap driver idle, pertimbangkan jarak hub→tujuan, arah, beban, ETA siap-pickup.
- State driver di-track real-time (lokasi, status: idle/picking/delivering). Simpan di Redis + geo-index (
GEOADD/GEOSEARCH) buat cari driver terdekat dari hub. - Batch delivery (multi-order satu driver) kalau searah → naikin throughput tapi naikin ETA order tertentu. Trade-off eksplisit yang interviewer suka denger.
Edge
Tidak ada driver available (jam sibuk) → queue + dynamic ETA + surge. Driver reject/cancel → re-assign cepat tanpa starvation order.
Potential Follow-up
- "Gimana cara deteksi driver yang sengaja lambat/mangkir?"
- "Bagaimana kalau driver lagi dalam perjalanan, tiba-tiba ada order baru yang lebih optimal?"
- "Apa bedanya assignment problem ini dengan ride-hailing (Gojek/Grab)?"
5. ETA Estimation ("tiba dalam X menit")
Janji utama Astro. ETA = waktu picking di hub + antrian dispatch + travel time.
Inti Desain
- Travel time bukan haversine/jarak lurus — pakai routing engine (OSRM/Google) atau model ML berbasis histori (traffic, jam, cuaca, hari).
- Picking time tergantung jumlah item & beban hub saat itu.
- Tampilkan ETA konservatif (under-promise) karena miss ETA = trust rusak. Ini keputusan produk yang punya konsekuensi engineering (buffer, percentile p80 bukan rata-rata).
Risk
ETA dipakai juga buat dispatch decision → kalau model bias, dispatch ikut salah.
Potential Follow-up
- "Gimana cara validasi akurasi ETA model?"
- "Apa metric yang dipakai untuk monitor ETA miss?"
- "Bagaimana handle ETA kalau driver tiba-tiba cancel di tengah jalan?"
6. Checkout, Payment & Distributed Transaction
Masalah: checkout menyentuh banyak service — reserve stok, buat order, charge payment (e-wallet/VA via PG eksternal), assign hub. Harus konsisten walau ada partial failure.
Inti Desain
- Saga pattern (orchestration lebih gampang di-trace daripada choreography): reserve stok → create order (PENDING) → call PG → on success confirm; on fail/timeout → compensate (release stok, cancel order).
- Idempotency wajib: payment callback dari PG bisa dobel/out-of-order. Pakai idempotency key di create-order & di handler callback. Webhook PG harus idempotent dan signature-verified.
- Outbox pattern buat publish event order (jangan dual-write DB + broker tanpa outbox → bisa lost/ghost event).
Risk/Security
Webhook spoofing → verifikasi signature; race antara user-cancel dan payment-success; double-charge.
Potential Follow-up
- "Kenapa orchestration bukan choreography?"
- "Gimana cara test saga rollback secara end-to-end?"
- "Apa yang terjadi kalau payment gateway timeout tapi di sisi mereka sukses?"
- "Bagaimana handle refund flow?"
7. Promo / Voucher Engine
Astro punya aturan promo non-trivial: gratis ongkir 2x pertama, min Rp100k, potongan Rp5k. Ini case rules + anti-abuse.
Inti Desain
- Rule engine: kondisi (min transaksi, first-N-order, area, kategori) → efek (discount, free ongkir). Stackable atau eksklusif? Urutan apply? Ini detail yang ditanya.
- Counter "berapa kali user sudah pakai" harus atomic & race-safe (user spam checkout paralel buat dobel promo).
- Anti-fraud: multi-akun by device, nomor HP, alamat, pembayaran. Promo "first order" dimanfaatin dengan akun baru terus.
Risk
Budget promo bisa jebol kalau ga ada cap; voucher race bisa over-redeem.
Potential Follow-up
- "Gimana cara desain rule engine yang bisa dikonfigurasi tim produk tanpa deploy?"
- "Apa perbedaan voucher-based vs automatic promo?"
- "Bagaimana deteksi abuse tanpa bikin false positive yang ganggu user genuine?"
8. Search & Catalog (15k SKU)
Masalah: search cepat + relevan, tapi hanya tampilkan produk yang in-stock di hub user.
Inti Desain
- Elasticsearch/OpenSearch buat full-text + typo tolerance (telor→telur) + autocomplete.
- Tantangan: filter availability per-hub. Index per-hub mahal. Pilihan: index katalog global, lalu filter stok dari Redis saat query-time, atau simpan stok flag di index dan update via stream (eventual). Trade-off freshness vs cost.
- Ranking: relevansi + ketersediaan + margin + popularitas.
Risk
Stok index basi → user lihat produk yang ternyata habis (UX buruk, balik ke masalah #2).
Potential Follow-up
- "Kenapa Elasticsearch bukan PostgreSQL full-text search?"
- "Gimana cara handle reindexing tanpa downtime?"
- "Bagaimana handle search saat traffic spike (jam makan siang/promosi)?"
9. Real-time Order Tracking
Masalah: user lihat driver bergerak di peta real-time.
Inti Desain
- Driver app kirim lokasi tiap beberapa detik → ingest (bisa via MQTT/WebSocket) → push ke user yang relevan.
- Jangan broadcast semua; fan-out hanya ke subscriber order itu. Pakai pub/sub keyed by order.
- Throttle & smoothing posisi; tahan beban dari ribuan driver concurrent.
Trade-off
Frekuensi update vs biaya bandwidth/server.
Potential Follow-up
- "Kenapa MQTT bukan WebSocket langsung?"
- "Gimana cara smooth posisi driver di map biar tidak lompat-lompat?"
- "Apa yang terjadi kalau driver matikan GPS/lokasi?"
10. Demand Forecasting & Replenishment
Lebih ke data/backend tapi sering muncul karena ini efisiensi modalnya.
Masalah: tiap hub harus stok barang yang bener supaya ga out-of-stock (lost sale) tapi ga overstock (fresh produce busuk).
Inti Desain
Forecast per-SKU per-hub (musiman, hari, cuaca, promo) → auto-replenishment order ke central warehouse. Fresh goods butuh handling expiry (FEFO — First Expired First Out).
Nilai plus: kalau pernah implement demand forecasting (Holt-Winters, ARIMA, Prophet) bisa diangkat sebagai pengalaman relevan.
Potential Follow-up
- "Apa metrik akurasi forecasting yang paling penting?"
- "Gimana handle cold-start (hub baru, SKU baru, tidak ada histori)?"
- "Bagaimana beda strategi replenishment untuk dry goods vs fresh produce?"
11. Dynamic Delivery Fee / Surge
Astro patok ongkir Rp15k dengan banyak promo. Case: ongkir dinamis berdasar jarak, beban driver, jam sibuk, cuaca.
Inti Desain
Pricing service ambil signal (supply driver vs demand order di area, jarak, weather) → hitung fee. Harus konsisten antara yang ditampilkan saat checkout dan yang di-charge (lock harga saat checkout, jangan berubah di tengah).
Potential Follow-up
- "Gimana cara pastiin harga yang dilihat user = harga yang di-charge?"
- "Apa elasticity demand untuk delivery fee? Gimana cara ukurnya?"
- "Bagaimana komunikasi perubahan fee ke user tanpa bikin churn?"
Artikel Terkait (News)
Setiap case punya artikel detail dengan Go code, Mermaid diagrams, dan system design lengkap:
#1 Flash Sale
Redis stock partitioning, waiting room, rate limiter, anti-bot → Go implementation
#2 Real-time Inventory
Reservation pattern, TTL, reaper job, async sync Redis→Postgres → Go implementation
#3 Geo Serviceability
H3 hexagon lookup, PostGIS polygon, multi-hub tie-break → Go implementation
#4 Driver Dispatch
Batching + greedy matching, GEOSEARCH, driver state machine → Go implementation
#5 ETA Estimation
Picking + queue + OSRM routing + ML model, p80 conservative → Go implementation
#6 Checkout Saga
Orchestration saga, idempotency, outbox pattern, webhook HMAC → Go implementation
#7 Promo Engine
Composable rule engine, atomic counter, anti-fraud fingerprint → Go implementation
#8 Search & Catalog
Elasticsearch fuzzy + Redis stock filter per-hub, autocomplete → Go implementation
#9 Real-time Tracking
WebSocketGPS → Kalman filter → Redis pub/sub fan-out → SSE push → Go implementation
#10 Demand Forecasting
Holt-Winters, Bayesian cold-start, FEFO, auto-replenishment → Go implementation
#11 Dynamic Delivery Fee
Signal-based pricing, price lock, surge detector, A/B testing → Go implementation
Prioritas Belajar (Waktu Terbatas)
- #1 Flash Sale — high-concurrency, hot key, stock partitioning, rate limiting, anti-bot
- #2 Inventory — jantung q-commerce, oversell prevention + reservation
- #4 Dispatch — assignment problem + real-time driver state
- #6 Checkout/Saga — distributed transaction + idempotency
- #3 Geo — serviceability + hub assignment
Lima ini paling "Astro banget" dan paling membedakan dari e-commerce biasa.
Catatan
Asumsi: role backend/fullstack (Go), interview-nya system design + behavioral, bukan pure algo. Kalau fokusnya ke spesifik lain (pure DSA, frontend, infra/k8s), case-nya berbeda.
Last updated on