What is Low Level Design
Low Level Design (LLD) is the process of defining the internal structure of a specific component or module — the classes, interfaces, their attributes and methods, and how they interact via design patterns. LLD answers: "How do we implement this feature in code?"
LLD vs HLD: High Level Design (HLD) defines the system architecture — which services exist, how they communicate, what databases they use, how they scale. LLD zooms in on one service or feature and answers how the code inside is structured. HLD is for the system; LLD is for the module.
What interviewers assess in an LLD round: - Can you identify the right entities (classes/interfaces) from requirements? - Do you understand object relationships (IS-A, HAS-A, association)? - Can you apply design patterns appropriately — not cargo-culted, but purposefully? - Is your design extensible without modification? - Do you handle edge cases (concurrency, invalid input, lifecycle)?
Typical LLD interview questions: Design a Parking Lot, Elevator System, Library Management System, Chess Game, Vending Machine, ATM, Online Auction, Movie Ticket Booking.
UML Class Diagrams
A UML class diagram is the standard way to communicate LLD. Each class is drawn as a box with three compartments: class name (top), attributes (middle), methods (bottom). Visibility prefixes: + public, - private, # protected.
Relationship arrows: - Inheritance (IS-A): solid line with a hollow triangle arrowhead pointing to the parent - Implementation (IS-A with interface): dashed line with hollow triangle to the interface - Composition (strong HAS-A): solid line with a filled diamond at the "whole" end - Aggregation (weak HAS-A): solid line with a hollow diamond at the "whole" end - Association: plain arrow (knows about, uses) - Dependency (uses temporarily): dashed arrow
Multiplicity notation: 1 (exactly one), * (zero or more), 0..1 (optional), 1..* (one or more). Place multiplicity at both ends of the relationship line.
For interviews, you don't need pixel-perfect UML. What matters: clearly name every class and interface, show key attributes (type + name), show public methods, and draw the right relationship type with multiplicity. Interviewers care about the design, not the syntax.
# Quick UML → code translation
# Interface (abstract base class)
class Vehicle:
def start(self): raise NotImplementedError
def stop(self): raise NotImplementedError
# Inheritance (IS-A): Car ──▷ Vehicle
class Car(Vehicle):
brand: str
model: str
def start(self): ...
def stop(self): ...
# Composition (filled diamond): Car ◆──── Engine
# Car creates and owns Engine; Engine cannot exist without Car
class Engine:
horsepower: int
fuel_type: str
def ignite(self): ...
class Car(Vehicle):
def __init__(self, brand, hp):
self.brand = brand
self._engine = Engine(hp, "petrol") # owns it
# Aggregation (hollow diamond): Garage ◇──── Car
# Garage holds Cars but doesn't own their lifecycle
class Garage:
def __init__(self):
self.cars = [] # cars exist independently
def park(self, car: Car):
self.cars.append(car) Creational Design Patterns
Creational patterns abstract the object creation process, making systems independent of how their objects are created, composed, and represented.
Singleton: ensures a class has only one instance and provides a global access point. Use for: configuration managers, thread pools, loggers, connection pools. Thread-safe in Python using a lock or module-level initialization.
Factory Method: defines an interface for creating an object but lets subclasses decide which class to instantiate. Use when you want to defer instantiation to subclasses. The creator doesn't know which concrete class will be instantiated.
Abstract Factory: creates families of related objects without specifying their concrete classes. Use when your system needs to be independent of how its products are created and when it needs to work with families of related objects (Windows UI vs Mac UI).
Builder: separates the construction of a complex object from its representation. Use when: an object requires many optional parameters (avoid the telescoping constructor anti-pattern), the construction process must allow different representations, you want step-by-step construction with validation.
Prototype: creates new objects by cloning an existing one. Use when object creation is expensive and a similar object already exists, or when classes to instantiate are specified at runtime.
import threading
# SINGLETON — thread-safe
class DatabaseConnection:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None: # double-checked locking
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if not self._initialized:
self.connection = "Connected to DB"
self._initialized = True
# FACTORY METHOD
class Notification:
def send(self, message): raise NotImplementedError
class EmailNotification(Notification):
def send(self, msg): return f"Email: {msg}"
class SMSNotification(Notification):
def send(self, msg): return f"SMS: {msg}"
class PushNotification(Notification):
def send(self, msg): return f"Push: {msg}"
def notification_factory(channel: str) -> Notification:
mapping = {"email": EmailNotification, "sms": SMSNotification, "push": PushNotification}
cls = mapping.get(channel)
if not cls: raise ValueError(f"Unknown channel: {channel}")
return cls()
# BUILDER
class QueryBuilder:
def __init__(self): self._table = ""; self._conditions = []; self._limit = None
def from_table(self, table):
self._table = table; return self
def where(self, condition):
self._conditions.append(condition); return self
def limit(self, n):
self._limit = n; return self
def build(self):
sql = f"SELECT * FROM {self._table}"
if self._conditions:
sql += " WHERE " + " AND ".join(self._conditions)
if self._limit:
sql += f" LIMIT {self._limit}"
return sql
q = QueryBuilder().from_table("users").where("active=1").where("age>18").limit(10).build()
print(q) # SELECT * FROM users WHERE active=1 AND age>18 LIMIT 10 Structural Design Patterns
Structural patterns deal with object composition — how to assemble objects and classes into larger structures while keeping those structures flexible and efficient.
Adapter: converts the interface of a class into another interface the client expects. Use when integrating legacy code or third-party libraries with incompatible interfaces. The adapter wraps the incompatible class and translates calls.
Decorator: adds behavior to an object dynamically without changing its class. Use when you want to add responsibilities to objects without subclassing. Python's @functools.lru_cache is a decorator. Coffee with milk, sugar, cream — each is a decorator adding cost and description.
Facade: provides a simplified interface to a complex subsystem. Use when a system is very complex and you want to provide a simpler interface for common tasks. A HomeTheaterFacade with watchMovie() hides DVD player, projector, amplifier, and lights setup.
Proxy: provides a surrogate or placeholder that controls access to another object. Types: virtual proxy (lazy loading), protection proxy (access control), remote proxy (local representative for remote object), caching proxy.
Composite: composes objects into tree structures to represent part-whole hierarchies. Let clients treat individual objects and compositions uniformly. A file system: File and Directory both implement FileSystemItem. A Directory contains FileSystemItems (which can be Files or other Directories).
Flyweight: uses sharing to support large numbers of fine-grained objects efficiently. Share common state (intrinsic) among many objects; keep unique state (extrinsic) outside. Example: a text editor sharing character format objects instead of creating one per character.
# ADAPTER — make incompatible interfaces work together
class EuropeanSocket:
def voltage(self): return 220
def live(self): return "L"
def neutral(self): return "N"
class USPlug:
def voltage(self): return 110
def hot(self): return "H"
def neutral(self): return "N"
class EuropeanToUSAdapter(USPlug):
def __init__(self, socket: EuropeanSocket):
self._socket = socket
def voltage(self): return self._socket.voltage() // 2 # simplified
def hot(self): return self._socket.live()
# DECORATOR — add behavior without subclassing
class Coffee:
def cost(self): return 2.0
def description(self): return "Plain coffee"
class MilkDecorator:
def __init__(self, coffee): self._coffee = coffee
def cost(self): return self._coffee.cost() + 0.5
def description(self): return self._coffee.description() + ", milk"
class SugarDecorator:
def __init__(self, coffee): self._coffee = coffee
def cost(self): return self._coffee.cost() + 0.25
def description(self): return self._coffee.description() + ", sugar"
c = Coffee()
c = MilkDecorator(c)
c = SugarDecorator(c)
print(c.description(), c.cost()) # Plain coffee, milk, sugar 2.75
# FACADE
class HomeTheaterFacade:
def __init__(self, dvd, projector, amplifier, lights):
self.dvd = dvd; self.proj = projector
self.amp = amplifier; self.lights = lights
def watch_movie(self, movie):
self.lights.dim(10)
self.proj.on(); self.proj.widescreen()
self.amp.on(); self.amp.set_volume(5)
self.dvd.on(); self.dvd.play(movie) Behavioral Design Patterns
Behavioral patterns are concerned with algorithms and the assignment of responsibilities between objects.
Observer: defines a one-to-many dependency so when one object changes state, all its dependents are notified automatically. Use for event systems, UI frameworks (MVC), stock tickers. Subject maintains a list of observers; all observers implement an update() interface.
Strategy: defines a family of algorithms, encapsulates each, and makes them interchangeable. Lets the algorithm vary independently from the clients that use it. Sorting strategies, payment methods, compression algorithms.
Command: encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. Remote controls, transaction systems, undo/redo.
State: allows an object to alter its behavior when its internal state changes. Appears as if the object changed its class. Vending machine states (idle, has_coin, dispensing), order states (placed, paid, shipped, delivered).
Template Method: defines the skeleton of an algorithm in a base class, deferring some steps to subclasses. Subclasses can override steps without changing the algorithm's structure. Data parsing pipelines, test frameworks.
Iterator: provides a sequential way to access elements of a collection without exposing its internal representation. Every Python for loop uses the iterator protocol (__iter__ and __next__).
Chain of Responsibility: passes a request along a chain of handlers. Each handler decides to process or pass it on. HTTP middleware, logging handlers, approval workflows.
# OBSERVER
class EventEmitter:
def __init__(self):
self._listeners = {}
def on(self, event, listener):
self._listeners.setdefault(event, []).append(listener)
def emit(self, event, *args):
for listener in self._listeners.get(event, []):
listener(*args)
emitter = EventEmitter()
emitter.on("data", lambda d: print(f"Handler 1: {d}"))
emitter.on("data", lambda d: print(f"Handler 2: {d}"))
emitter.emit("data", {"user": "Alice"})
# STRATEGY
class Sorter:
def __init__(self, strategy):
self._strategy = strategy
def sort(self, data):
return self._strategy(data)
quick = Sorter(sorted)
bubble = Sorter(lambda d: sorted(d, reverse=True))
# STATE — Vending Machine
class VendingMachine:
def __init__(self):
self.state = "idle"
self.balance = 0
def insert_coin(self, amount):
if self.state != "idle": return
self.balance += amount
self.state = "has_coin"
print(f"Inserted {amount}. Balance: {self.balance}")
def select_item(self, price):
if self.state != "has_coin": print("Insert coin first"); return
if self.balance < price: print("Insufficient balance"); return
self.balance -= price
self.state = "dispensing"
print("Dispensing item...")
self.state = "idle"
def cancel(self):
if self.state == "has_coin":
print(f"Returning {self.balance}")
self.balance = 0; self.state = "idle" LLD Interview: Design a Parking Lot
Requirements clarification: How many floors? Multiple vehicle types (motorcycle, car, truck)? Pricing model (flat rate, hourly)? Entry/exit points? Need to track availability in real time?
Core entities: ParkingLot, ParkingFloor, ParkingSpot (types: COMPACT, LARGE, MOTORCYCLE), Vehicle (subclasses: Car, Truck, Motorcycle), Ticket (assigned on entry), ParkingAttendant, PaymentService.
Class relationships: ParkingLot HAS-A list of ParkingFloors. Each ParkingFloor HAS-A list of ParkingSpots. A ParkingSpot HAS-A optional Vehicle (when occupied). A Ticket HAS-A Vehicle, a ParkingSpot, and an entry timestamp.
Key methods: ParkingLot.find_spot(vehicle_type) → ParkingSpot, ParkingLot.park(vehicle) → Ticket, ParkingLot.exit(ticket) → Payment, ParkingSpot.is_available() → bool, PaymentService.calculate_fee(ticket) → amount.
Design decisions: Use Strategy pattern for pricing (HourlyPricing, FlatRatePricing). Use Observer to update a display board when spot availability changes. Spot assignment: iterate floors top-to-bottom to preserve ground-floor spots for handicapped; or use a priority queue keyed by floor number.
Edge cases: What if all spots are full? What happens if a ticket is lost? How do you handle a vehicle larger than its assigned spot class? Concurrent entry from multiple entry points — thread-safe spot assignment needed.
from enum import Enum
from datetime import datetime
class SpotType(Enum):
MOTORCYCLE = 1
COMPACT = 2
LARGE = 3
class VehicleType(Enum):
MOTORCYCLE = 1
CAR = 2
TRUCK = 3
VEHICLE_TO_SPOT = {
VehicleType.MOTORCYCLE: SpotType.MOTORCYCLE,
VehicleType.CAR: SpotType.COMPACT,
VehicleType.TRUCK: SpotType.LARGE,
}
class ParkingSpot:
def __init__(self, spot_id, spot_type: SpotType):
self.id = spot_id
self.type = spot_type
self.vehicle = None
def is_available(self): return self.vehicle is None
def assign(self, vehicle): self.vehicle = vehicle
def free(self): self.vehicle = None
class Ticket:
def __init__(self, vehicle, spot):
self.vehicle = vehicle
self.spot = spot
self.entry_time = datetime.now()
class ParkingLot:
def __init__(self):
self._spots = {t: [] for t in SpotType}
def add_spot(self, spot: ParkingSpot):
self._spots[spot.type].append(spot)
def park(self, vehicle_type: VehicleType, vehicle) -> Ticket:
spot_type = VEHICLE_TO_SPOT[vehicle_type]
for spot in self._spots[spot_type]:
if spot.is_available():
spot.assign(vehicle)
return Ticket(vehicle, spot)
raise Exception("No available spots")
def exit(self, ticket: Ticket) -> float:
duration = (datetime.now() - ticket.entry_time).seconds / 3600
ticket.spot.free()
return max(1.0, duration * 2.0) # $2/hour, minimum $1 LLD Interview: Design an Elevator System
Requirements: N elevators, M floors. Each elevator has a direction (UP, DOWN, IDLE) and a current floor. Passengers press external hall buttons (floor + direction) and internal cabin buttons (destination floor). Goal: minimize wait time.
Core entities: Building, Elevator, ElevatorController, Request (HallCall or CabinCall), Door.
Elevator states: IDLE, MOVING_UP, MOVING_DOWN, DOOR_OPEN. Use the State pattern to manage transitions cleanly.
Scheduling algorithm — SCAN (elevator algorithm): the elevator moves in one direction servicing all requests on the way, then reverses. Variants: LOOK (reverses when no more requests in current direction), SSTF (shortest seek time first — may starve distant requests), FCFS (simple but inefficient).
ElevatorController responsibilities: receive hall calls, assign the best elevator (by proximity + direction compatibility), maintain a sorted set of pending floors for each elevator, emit movement commands.
Key methods: ElevatorController.request(floor, direction) → assigns to best elevator. Elevator.add_destination(floor). Elevator.move() — advances one floor per tick. Elevator.open_door() / close_door().
Edge cases: elevator at capacity (weight sensor), emergency stop, door obstruction sensor, fire alarm (send all to ground floor).
from enum import Enum
class Direction(Enum):
UP = 1; DOWN = -1; IDLE = 0
class Elevator:
def __init__(self, elevator_id, total_floors):
self.id = elevator_id
self.current_floor = 0
self.direction = Direction.IDLE
self.destinations = set()
self.total_floors = total_floors
def add_destination(self, floor: int):
self.destinations.add(floor)
self._update_direction()
def _update_direction(self):
if not self.destinations:
self.direction = Direction.IDLE
elif max(self.destinations) > self.current_floor:
self.direction = Direction.UP
else:
self.direction = Direction.DOWN
def step(self):
if self.direction == Direction.IDLE:
return
self.current_floor += self.direction.value
if self.current_floor in self.destinations:
self.destinations.remove(self.current_floor)
print(f"Elevator {self.id}: doors open at floor {self.current_floor}")
self._update_direction()
class ElevatorController:
def __init__(self, num_elevators, total_floors):
self.elevators = [Elevator(i, total_floors) for i in range(num_elevators)]
def request(self, floor: int, direction: Direction):
best = min(self.elevators, key=lambda e: abs(e.current_floor - floor))
best.add_destination(floor)
return best.id LLD Interview: Design a Library Management System
Requirements: catalog management (books, magazines), member registration, book search, borrow and return, reservations, fine calculation, librarian vs member roles.
Core entities: Library, Book, BookItem (physical copy — a Book can have many BookItems), Member, Librarian, BorrowRecord, Reservation, Fine, Catalog.
A Book is the abstract record (ISBN, title, author, genre). A BookItem is a physical instance (barcode, shelf location, condition). One Book can have 3 copies = 3 BookItems.
Key relationships: Catalog HAS-A list of Books. Library HAS-A Catalog, list of Members, list of BorrowRecords. Member HAS-A list of active BorrowRecords (max 5). BorrowRecord HAS-A BookItem + Member + due_date.
Key methods: Catalog.search(query) → List[Book], Library.borrow(member, book_item) → BorrowRecord, Library.return_book(borrow_record) → Fine or None, Member.reserve(book) → Reservation, Fine.calculate(due_date, return_date) → amount.
Design patterns: Use Observer to notify waiting members when a book becomes available (reservation queue). Use Strategy for fine calculation (different policies: $0.25/day, waived for first offence). Use Factory for creating different account types (Member vs Librarian).
from datetime import datetime, timedelta
from enum import Enum
class BookStatus(Enum):
AVAILABLE = "available"
BORROWED = "borrowed"
RESERVED = "reserved"
class Book:
def __init__(self, isbn, title, author):
self.isbn = isbn; self.title = title; self.author = author
self.copies = []
def add_copy(self, copy): self.copies.append(copy)
def available_copies(self):
return [c for c in self.copies if c.status == BookStatus.AVAILABLE]
class BookCopy:
def __init__(self, barcode, book: Book):
self.barcode = barcode
self.book = book
self.status = BookStatus.AVAILABLE
class Member:
MAX_BOOKS = 5
def __init__(self, member_id, name):
self.id = member_id; self.name = name
self.active_borrows = []
def can_borrow(self): return len(self.active_borrows) < self.MAX_BOOKS
class BorrowRecord:
LOAN_DAYS = 14
FINE_PER_DAY = 0.25
def __init__(self, copy: BookCopy, member: Member):
self.copy = copy; self.member = member
self.borrow_date = datetime.now()
self.due_date = self.borrow_date + timedelta(days=self.LOAN_DAYS)
self.return_date = None
def fine(self) -> float:
if not self.return_date or self.return_date <= self.due_date:
return 0.0
overdue = (self.return_date - self.due_date).days
return overdue * self.FINE_PER_DAY
class Library:
def __init__(self):
self.catalog = {} # isbn → Book
def add_book(self, book: Book): self.catalog[book.isbn] = book
def borrow(self, member: Member, isbn: str) -> BorrowRecord:
if not member.can_borrow(): raise Exception("Borrow limit reached")
book = self.catalog.get(isbn)
if not book: raise Exception("Book not found")
copies = book.available_copies()
if not copies: raise Exception("No copies available")
copy = copies[0]
copy.status = BookStatus.BORROWED
record = BorrowRecord(copy, member)
member.active_borrows.append(record)
return record LLD Interview: Design a Vending Machine
Requirements: Multiple products at different prices, coin/bill insertion, product selection, change dispensal, refund, admin restock, display current balance.
State machine approach (State pattern): VendingMachine transitions through states: IDLE → HAS_MONEY → DISPENSING → CHANGE_RETURN → IDLE. Each state handles inputs differently. Attempting to select a product in IDLE state is a no-op.
Core entities: VendingMachine, Product, Inventory, CoinSlot, Display, StateHandler (Idle, HasMoney, Dispensing).
Key transitions: insert_coin(amount) in IDLE → HAS_MONEY. select_product(id) in HAS_MONEY — if balance >= price → DISPENSING. dispense() → CHANGE_RETURN. return_change() → IDLE. cancel() in any state → returns balance → IDLE.
Inventory management: products indexed by slot ID, each slot has a count. Sold-out products should be greyed out on the display.
Edge cases: exact change required (can the machine make change?), power failure mid-transaction, coin jammed, product stuck in dispenser chute.
from abc import ABC, abstractmethod
class Product:
def __init__(self, name, price):
self.name = name; self.price = price
class VendingMachineState(ABC):
@abstractmethod
def insert_coin(self, machine, amount): pass
@abstractmethod
def select_product(self, machine, slot_id): pass
@abstractmethod
def cancel(self, machine): pass
class IdleState(VendingMachineState):
def insert_coin(self, machine, amount):
machine.balance += amount
machine.state = machine.has_money_state
print(f"Balance: {machine.balance:.2f}")
def select_product(self, machine, slot_id):
print("Please insert coins first")
def cancel(self, machine):
print("Nothing to cancel")
class HasMoneyState(VendingMachineState):
def insert_coin(self, machine, amount):
machine.balance += amount
print(f"Balance: {machine.balance:.2f}")
def select_product(self, machine, slot_id):
product = machine.inventory.get(slot_id)
if not product: print("Invalid slot"); return
if machine.balance < product.price:
print(f"Need {product.price - machine.balance:.2f} more"); return
machine.balance -= product.price
print(f"Dispensing {product.name}. Returning {machine.balance:.2f}")
machine.balance = 0
machine.state = machine.idle_state
def cancel(self, machine):
print(f"Returning {machine.balance:.2f}")
machine.balance = 0
machine.state = machine.idle_state
class VendingMachine:
def __init__(self):
self.idle_state = IdleState()
self.has_money_state = HasMoneyState()
self.state = self.idle_state
self.balance = 0.0
self.inventory = {}
def load_product(self, slot_id, product: Product):
self.inventory[slot_id] = product
def insert_coin(self, amount): self.state.insert_coin(self, amount)
def select(self, slot_id): self.state.select_product(self, slot_id)
def cancel(self): self.state.cancel(self) LLD Interview Framework
A repeatable 45-minute LLD interview framework. Interviewers want to see structured thinking, not just code.
Minutes 0–5: Requirements clarification. Ask about: scale (how many users?), supported features (what's in scope?), constraints (any performance SLAs?). Write down the functional requirements you'll design for. This prevents scope creep and shows systematic thinking.
Minutes 5–10: Identify core entities. List nouns in the requirements — these become your classes. Distinguish between entities (things with identity and lifecycle) and value objects (immutable data without identity). Group related attributes.
Minutes 10–20: Define class relationships. Draw the class diagram. Show inheritance (IS-A), composition (filled diamond), and aggregation (hollow diamond). Add multiplicity. Identify interfaces vs abstract classes. This is where most marks are won or lost.
Minutes 20–35: Key methods and design patterns. For each major use case, walk through which methods are called. Identify where patterns add value: Factory for creation, Observer for events, Strategy for algorithms, State for state machines. Write pseudocode for 1–2 core methods.
Minutes 35–45: Edge cases and extensibility. Concurrency (thread-safe methods?). Error handling (what if the DB is down?). Extensibility (what if we add a new vehicle type? a new payment method?). How would you test this? What metrics would you monitor?
Common LLD Interview Questions
These are the top 20 LLD questions and the key design points for each.
Design a Parking Lot — entities: ParkingSpot (type), Vehicle (type), Ticket, ParkingFloor. Patterns: Strategy (pricing), Observer (display board).
Design an Elevator System — entities: Elevator, Request, ElevatorController. Patterns: State (elevator state), Strategy (scheduling algorithm).
Design a Library Management System — entities: Book, BookCopy, Member, BorrowRecord. Patterns: Observer (notify on return), Strategy (fine calculation).
Design a Vending Machine — entities: Product, Inventory, CoinSlot. Patterns: State (idle/has_money/dispensing).
Design an ATM — entities: Card, Account, Transaction, CashDispenser. Patterns: State (idle/has_card/authenticated/transaction), Strategy (auth method).
Design a Chess Game — entities: Board, Piece (subclasses: King, Queen, Rook, Bishop, Knight, Pawn), Player, Move. Patterns: Strategy (piece movement rules).
Design Online Auction — entities: Item, Bid, Auction, Bidder, AuctionNotifier. Patterns: Observer (notify bidders on new bid), Strategy (auction type: English, Dutch).
Design Movie Ticket Booking — entities: Movie, Show, Seat, Booking, Payment. Patterns: Singleton (seat availability cache), Strategy (pricing: normal, premium).
Design a Hotel Booking System — entities: Hotel, Room (type), Booking, Guest. Patterns: Builder (complex search query), Strategy (pricing).
Design a Food Delivery System — entities: Restaurant, MenuItem, Order, DeliveryAgent, Customer. Patterns: Observer (order status updates), Strategy (delivery assignment).
Design a Social Network (basic) — entities: User, Post, Comment, Like, Follow. Patterns: Observer (news feed updates), Iterator (paginated feed).
Design a Ride-Sharing App — entities: Driver, Rider, Trip, Location, PricingEngine. Patterns: Strategy (pricing: surge, flat), Observer (location updates).