Estimate the scale of the system you are going to design...
# Current weather
GET api/v1/weather/{zipcode}?appid={API key}&units={units}&lang={lang}
Response:
{
"zipcode": "...",
"timezone": "US/Boston",
"current": {
"dt": {timestamp UTC},
"sunrise": {timestamp UTC}
"temperature": 23,
"windspeed": 54,
"winddirection": "NW",
"condition": "partly_cloudy",
},
"metadata": {
"lastUpdated": "timestampUTC",
"unit": "SI",
"source": "nsw.com"
}
}
# Weather forecast
GET api/v1/weather/{zipcode}/forecast
{
"zipcode" : "",
"timezone": "US/Boston",
"forcaset": [
{
"time": {timestamp UTC},
"Temperature": 23,
"windspeed": 54,
"winddirection": "NW",
"condition": "partly_cloudy",
},
...
],
"metadata": {
"unit": "SI",
"lang": "EN",
"source": "nsw.com"
}
}
# Historical data
GET api/v1/weather/{zipcode}/history?start_date={UTC}&end_date={UTC}&units={units}&lang=EN&appid={API Key}
{
"zipcode" : "",
"timezone": "US/Boston",
"hourly": [
{
"time": {timestamp UTC},
"Temperature": 23,
"windspeed": 54,
"winddirection": "NW",
"condition": "partly_cloudy",
},
...
],
"daily": [
{
"time": {timestamp UTC},
"Temperature": 23,
"windspeed": 54,
"winddirection": "NW",
"condition": "partly_cloudy",
},
...
],
"metadata": {
"unit": "SI",
"lang": "EN",
"source": "nsw.com"
}
}
# Alerts
GET api/v1/weather/{zipcode}/alerts
{
"zipcode": 2342,
"timezone": "US/Boston"
"alerts":
[
{
"id": 1242343,
"sender_name": "NWS Massachusetts advisory",
"event": "Storm advisory",
"start_time": {UTC},
"end_time": {UTC},
"description": "....",
"tags": ["storm",...]
},
...
]
}
# User preferences
GET api/v1/users/{userId}/favorites
{
"userId": "...",
"unit": "metric",
"lang": "EN",
"zipcodes": ["341", "34534", ..]
}
POST api/v1/users/{userId}/favorites
Defining the system data model early on will clarify how data will flow among different components of the system. Also you could draw an ER diagram using the diagramming tool to enhance your design...
User preferences Table
userID string PK
unit string
lang string
zipcodes [string,..]
Forecast Table
zipcode string PK
forecast_time timestamp PK
timezone string
temperature float
windspeed float
sunrise timestamp
unit string
source string
Weather Table
zipcode string PK
recorded_at timestamp PK
timezone string
temparture float
humidty float
sunrise timestamp
condition string
unit string
zone preference Table
{
"user_id": "",
"zipcode": "23234",
"preferred_zone": "simple",
"last_updated": timestamp
}
User preferences Table
{
"user_id": "",
"unit": "metric",
"lang": "EN",
"zipcodes": ["234","456"]
}
zip code metadata:
{
zip_code: ''3453",
"type": "simple",
"coordinates": {
"lat": 2312,
"lon": 4564
},
"city": "Boston",
"state": "MA",
"timezone": "US/NewYork",
"updated_at": ISO 8601
}
OR
{
zip_code: ''3453",
"type": "complex",
"zones": [
{
"name": "Valley Area",
"coordinates": {
"lat": 2312,
"lon": 4564
},
"elevation": 3453,
"is_primary": true
},
]
"city": "Loyalton",
"state": "MA",
"timezone": "US/NewYork",
"updated_at": ISO 8601
}
You should identify enough components that are needed to solve the actual problem from end to end. Also remember to draw a block diagram using the diagramming tool to augment your design. If you are unfamiliar with the tool, you can simply describe your design to the chat bot and ask it to generate a starter diagram for you to modify...
Explain how the request flows from end to end in your high level design. Also you could draw a sequence diagram using the diagramming tool to enhance your explanation...
Dig deeper into 2-3 components and explain in detail how they work. For example, how well does each component scale? Any relevant algorithm or data structure you like to use for a component? Also you could draw a diagram using the diagramming tool to enhance your design...
Rate limit on external API:
- if we have 60 calls per minute, we want to use token bucket algorithm, each seconds
a token is added, bucket hold 6o tokens. Each API call consumes 1 token and if bucket is emty
requests wait.
The backgrounder worker continously process requests in the queue while respecting
API rate limit by checking rate limit status:
{
"maxCallsPerminute": 60,
"currentWindow" : {
"startTime": ....,
"callsMade": 45,
"callsRemained": 15,
}
}
Cache:
- we first check cache:
- Hit: and if it's fresh return, if stale do backgrounder refresh and queue update
- miss: fetch from DB, if found update cache. if not found, external call , queue request
Backgrounder:
when the weather service needs a new data, it doesnt directly call the external API, it creates
an "update request" and puts it in the queue:
{
"requestid": "",
"zipcode": "13413",
"zonetype" : "complex"
"zones": [
{
"name": "Valley Area",
"coordinates": ["lat":...., "lon": ...]
},...
]
"priority": "high",
"requested_at": "2025-01-01T08:00:00Z"
}
the message queue organize based on priority:
- high priority: areas with active weather alerts or high user traffic or high weather volatility
- low priority: regular update for simple zipcodes with stable weather
- medium priority: regular update for complex zip codes
the queue look like this:
{
"high_priority": [
...],
"low_priority": [
...],
}
Zip code to grid points and vice versa:
- ZIP code boundaries can change over time and and represent irregular polygon area
- External API provides data based on grid points and each grid point represent 2.5lm x 2.5 km of area.
- if external API accepts zip code ...
- if external API doesn't accept zip code, we use Google geocode and pass in zip code to get lat and lon and then use it to call external API and we can save that into in our tables
However, the zip code is dense urban area can be fairly accurately mapped to a populated lat,lon as the weather doesn't change within a zipcode or evern nearby zip codes (backgrounder can potentially group nearby urban zip codes when making req to external API)
but that's not accurate for mountaineous area with large elevation changes or remote location where a zip code can cover a vast area.
zip code metadata:
{
zip_code: ''3453",
"type": "simple",
"coordinates": {
"lat": 2312,
"lon": 4564
},
"city": "Boston",
"state": "MA",
"timezone": "US/NewYork",
"updated_at": ISO 8601
}
OR
{
zip_code: ''3453",
"type": "complex",
"zones": [
{
"name": "Valley Area",
"coordinates": {
"lat": 2312,
"lon": 4564
},
"elevation": 3453,
"is_primary": true
},
]
"city": "Loyalton",
"state": "MA",
"timezone": "US/NewYork",
"updated_at": ISO 8601
}
when client sends a req for a zip code, we first fetch the metadata and if it's complex, we ask which one and we persist the preference for that user and zip code
Explain any trade offs you have made and why you made certain tech choices...
Try to discuss as many failure scenarios/bottlenecks as possible.
What are some future improvements you would make? How would you mitigate the failure scenario(s) you described above?