Structured, semi-structured and unstructured data

A single food order creates a neat table row, a nested JSON event, log lines from the rider's phone, a free-text review and a photo. These are the three shapes of data: structured, semi-structured and unstructured. In this lesson you flatten JSON into rows, parse logs, turn chats into numbers during an outage, and see how an app update can silently break a pipeline.

Module 2 · Data and Storage⏱ 28 minBeginner → AdvancedRuns in your browser (Python)3 real-time scenariosAll three shapes on your PC9 practice problems
1 The story

Six files, and only one opens in Excel

Kabir’s internship at TiffinGo starts next semester. To help him prepare, the data team sends a small practice dump: tiffingo_sample.zip. He unzips it on his laptop and finds six very different files. He tries to open all of them in Excel. Only one opens properly.

tiffingo_sample.ziporders.csvapp_events.jsonrider_gps.logsupport_chats.txtmenu_photos/invoices.pdfStructuredrows and columnsorders.csvSemi-structuredlabels, flexible shapeapp_events.jsonrider_gps.logUnstructuredno fixed modelsupport_chats.txtmenu_photos/invoices.pdf One company, one day, three very different shapes of data. myphatshala.in
Kabir

Sir, the CSV opened fine. The JSON file is one giant line of brackets, the log file is just text, and there’s a folder of photos and a PDF. How do I put this into one table?

Arjun Sir

You don’t, at least not directly. You’ve just met the three shapes of data: structured, semi-structured and unstructured. Each needs a different way to store, read and query.

Riya

Sir, in Lesson 1.1 you said most new data is unstructured. So is the CSV the odd one out?

Arjun Sir

In volume, yes. Photos, videos, chats and documents are the biggest part of the world’s data. But most business reports still run on structured tables. A big part of a data engineer’s job is turning the messy shapes into tidy tables.

Kabir

So the JSON and the logs are somewhere in between?

Arjun Sir

Exactly. They have labels but no fixed table shape. Today you’ll flatten a nested JSON order into rows, pull fields out of log lines, turn support chats into numbers, and see what happens when an app update quietly changes the shape of the data.

2 Definition

Structured, semi-structured and unstructured data

Formal definition (write this in exams)

Structured data conforms to a predefined schema of rows and columns with fixed data types, such as tables in a relational database. Semi-structured data does not follow a fixed tabular schema but contains tags, keys or markers that separate fields and describe hierarchy, so each record is self-describing; examples are JSON, XML, YAML and log files. Unstructured data has no predefined data model or organisation, such as free text, images, audio, video and scanned documents, and must be processed (parsed, tagged or analysed) before it can be queried.

In simple words

Structured: neat rows and columns, every row the same shape. Semi-structured: every value has a label, but records can have different labels and lists inside them. Unstructured: no labels at all, like a photo or a paragraph of text. You have to look inside to find out what it says.

Words you’ll meet in this lesson

SchemaThe list of fields and their data types: order_id: int, city: text, amount: decimal.
Self-describingEach record carries its own field names, like {"city": "Pune"}.
Nested dataA record that contains another record or a list, such as an order containing a list of items.
Flatten / explodeTurn nested data into flat rows, typically one row per item in a list.
ParsingReading text and splitting it into fields, such as pulling the date and status code out of a log line.
MetadataData about data: a photo’s size, format, upload time and location.
Schema driftWhen the shape of incoming data changes without warning.
EmbeddingA list of numbers that represents the meaning of a text, image or sound, used for AI search.

Common formats of each shape

StructuredDatabase tables (MySQL, PostgreSQL, Oracle), CSV with a fixed header, spreadsheet tables, Parquet/ORC tables
Semi-structuredJSON, XML, YAML, Avro and Protobuf messages, server and app logs, emails (headers + body), NoSQL documents
UnstructuredFree text, PDFs and scanned documents, images (JPG, PNG), audio (MP3, WAV), video (MP4), CCTV feeds

Three shapes of data at three levels

Pick your level. New to this? Start at Beginner and climb.

Beginner A spice rack, a tiffin box and a grocery bag

  • Structured is a spice rack: every jar has a fixed place and a printed label. You find turmeric instantly. Example: a marks sheet, a bank statement table.
  • Semi-structured is a tiffin box: each compartment is labelled (“dal”, “rice”), but one day there are three compartments and another day four. Example: a JSON message from an app.
  • Unstructured is a bag of groceries: everything is in there, but there are no labels. You must look at each item to know what it is. Example: a photo, a voice note, a free-text review.

Quick test: can you write column names for it without looking at every item? Yes → structured. Names exist but vary → semi-structured. No names at all → unstructured.

Intermediate Schemas, nesting and how each shape is queried

  • Schema: the description of fields and their types. Structured data has it before storing (schema-on-write); semi-structured data carries field names inside each record (self-describing); unstructured data has none.
  • Nesting: JSON can hold lists inside records (an order with three items). To analyse it in a table, you flatten it: one row per item, repeating the order fields.
  • Logs are text lines with a predictable pattern, so they are parsed into fields with split or regular expressions.
  • Unstructured data is made usable by adding metadata (file name, size, upload time, camera details) and by extracting features: words from text, labels from images, transcripts from audio.
  • Query tools: SQL for tables; JSON functions in databases, MongoDB queries or Spark for semi-structured; search engines (Elasticsearch, OpenSearch) and ML models for unstructured.

Advanced Schema drift, variant columns and embeddings

  • Schema drift: semi-structured sources change silently: new keys, missing keys, renamed fields, a number arriving as text. Pipelines need schema checks and data contracts (an agreed schema between the team producing data and the team using it).
  • Warehouses store JSON natively: Snowflake’s VARIANT, BigQuery’s JSON and STRUCT/ARRAY types, and Spark’s struct, array and map columns let you query nested data without flattening everything first.
  • Explode carefully: flattening two independent arrays in one record multiplies rows (3 items × 2 coupons = 6 rows), which silently double-counts money.
  • Unstructured data for AI: text, images and audio are converted into embeddings (lists of numbers that capture meaning) and stored in vector indexes, so you can search “photos of burnt rotis” or “complaints about late delivery” by meaning.
  • Size vs value: unstructured data is the largest share of storage, but structured data often drives most decisions. Plan storage costs and processing effort for each shape separately.

Exam tip: Draw a three-column table: definition, schema, examples, storage, tools to process, ease of analysis. Give two examples of each kind from one business (for example, a bank: transactions table, mobile-app JSON logs, cheque images). That earns full marks on most “types of big data” questions.

3 Types and ways to use it

Three shapes, compared

Structured

Fixed columns and types. Easy to store, index and query with SQL. Bank transactions, orders, marks, inventory.

Rows and columns

Semi-structured

Labels inside each record, shape can vary, can be nested. App events, API responses, server logs, sensor messages.

Keys and nesting

Unstructured

No predefined fields. Needs extra processing (OCR, speech-to-text, image models, text analysis) before analysis.

No fixed model

Side by side

StructuredSemi-structuredUnstructured
SchemaFixed, defined before storingFlexible, inside each recordNone
ExampleOrders tableApp event JSON, logsReview text, menu photos, call recordings
Stored inRDBMS, warehouse, Parquet tablesDocument DBs (MongoDB), lake files, Kafka topicsObject storage (S3, ADLS, GCS), HDFS
Queried withSQLJSON functions, Spark, MongoDB queriesSearch engines, ML models, OCR, speech-to-text
Ease of analysisEasiestMedium: parse and flatten firstHardest: extract features first
Share of total volumeSmallestLargeLargest (most estimates put it at a big majority)

Where data comes from

SourceWhat it producesUsual shape
Business transactionsOrders, payments, invoices, bookingsStructured
Machines and appsServer logs, clickstreams, API responsesSemi-structured
Sensors and IoTGPS pings, smart-meter readings, temperatureSemi-structured (often time series)
PeopleReviews, chats, emails, social posts, photos, voice notesUnstructured
DocumentsScanned forms, contracts, invoices as PDFUnstructured

The same fact can arrive in all three shapes: “order O1043 cost ₹420” can be a row in a table, a field in a JSON event, and a sentence in a customer’s complaint email. Good pipelines link them with a common key such as the order id.

4 Step by step

One food order in every shape

  1. Structured: one row per order

    import csv, io
    text = "order_id,city,amount\nO1043,Pune,420\nO1044,Mumbai,250\n"
    for row in csv.DictReader(io.StringIO(text)):
        print(row["order_id"], row["city"], int(row["amount"]))
    O1043 Pune 420 O1044 Mumbai 250
    Every row has the same three columns, and the types are known. This is the easiest shape: load it into a database and query it with SQL.
  2. Semi-structured: an order with a list inside

    import json
    event = json.loads('{"order_id": "O1043", "city": "Pune", "items": [{"dish": "thali", "qty": 1, "price": 220}, {"dish": "lassi", "qty": 2, "price": 100}]}')
    for item in event["items"]:
        print(event["order_id"], item["dish"], item["qty"] * item["price"])
    O1043 thali 220 O1043 lassi 200
    The JSON carries its own field names and contains a nested items list. Looping over the list flattens it into one row per item, ready for a table.
  3. Semi-structured: a log line with a pattern

    line = "2026-10-19 13:02:11 rider=R77 order=O1043 lat=18.5204 lon=73.8567 status=PICKED"
    parts = line.split()
    fields = dict(p.split("=") for p in parts[2:])
    print(parts[0], parts[1], fields["order"], fields["status"])
    2026-10-19 13:02:11 O1043 PICKED
    A log line is text, but it follows a pattern: date, time, then key=value pairs. Splitting by spaces and = turns it into fields.
  4. Unstructured: a free-text review

    review = "Thali was great but the delivery was very late and the lassi was warm"
    words = review.lower().split()
    labels = [w for w in ["late", "cold", "warm", "great", "spilled"] if w in words]
    print("labels:", labels)
    labels: ['late', 'warm', 'great']
    The review has no fields. Here a simple keyword list extracts labels; real systems use language models to detect sentiment and topics. The result is structured: order_id, label.
  5. Unstructured: a photo of the food

    A 3 MB photo arrives with the review. It is stored as a file in object storage, and a row of metadata is saved: file path, size, upload time, order id. An image model can add labels such as “thali, spilled”.

    The photo itself never goes into a table. Its path and labels do, so analysts can count “spilled food” complaints by kitchen.
  6. Link everything by order id

    Now all shapes can be joined: the order row, its items from JSON, the rider’s pickup time from logs, the review labels and the photo metadata, all keyed by O1043.

    This is the daily work of a data engineer: bring every shape into a structured, joinable form without losing the raw originals.
5 See it happen

One order: what Riya sees, and what the data looks like

Step through the six frames. Left: Riya ordering food. Right: the shape of the data each moment creates.

What Riya sees, and what the data looks like 6 steps
What you see Your orderThali × 1₹220Lassi × 2₹200Total₹420PLACE ORDEROrder confirmedOrder O1043Annapurna KitchenRider on the wayArriving in 12 minRate your order★★★☆☆“Great thali, butvery late and thelassi was warm”+ photoHelpLassi spilled!Sorry! ₹100 refundThanksYour month23 ordersFavourite: Thali myphatshala.in
Behind the screen Structured roworder_idkitchencityamountO1043K11Pune420Fixed columns, fixed types.Goes straight into the orders table.Semi-structured event (JSON){"order_id": "O1043", "user": {"id": "U9", "app": "5.2"}, "items": [ {"dish": "thali", "qty": 1}, {"dish": "lassi", "qty": 2}], "coupon": null}Labels inside, with a list nested in it.Semi-structured log lines13:02:11 rider=R77 lat=18.5204 lon=73.856713:02:16 rider=R77 lat=18.5211 lon=73.855913:02:21 rider=R77 lat=18.5219 lon=73.8551… one line every 5 seconds per riderSame pattern on every line: parse into fields.Thousands of riders → lakhs of lines an hour.Unstructured review + photo“Great thali, butvery late and thelassi was warm”IMG_2231.jpg · 3 MBextracted: sentiment = mixed · labels = late, warmphoto → s3://…/IMG_2231.jpg (metadata row)Unstructured chat → structured ticketLassi spilled!Sorry! ₹100 refundThanksorder: O1043topic: spilledrefund: 100resolved: yesA language model labels each chat.Everything joined by order_idO1043order rowitems (JSON)rider logsreview + chatOne customer view built from three shapes.Raw originals kept in the lake. myphatshala.in
6 Real-world use

Real-time scenarios: working with every shape

Three live scenarios: flattening nested JSON into a table, turning a flood of support chats into numbers during an outage, and an advanced one about an app update that silently changes the data.

Real-time scenario 1

Flatten a nested order into a table

TiffinGo’s app sends every order as nested JSON. The finance dashboard needs a flat table. Watch the three steps every pipeline uses, and the classic mistake to avoid at the end.

JSON → rows 5 steps
Animated example {"order_id": "O1043", "city": "Pune", "items": [ {"dish": "thali", "qty": 1, "price": 220}, {"dish": "lassi", "qty": 2, "price": 100}, {"dish": "gulab jamun", "qty": 1, "price": 60}], "coupon": null}A nested JSON order: three items inside one record.A table needs one value per cell. How do we get there?{"order_id": "O1043", "city": "Pune", "items": [ {"dish": "thali", "qty": 1, "price": 220}, {"dish": "lassi", "qty": 2, "price": 100}, {"dish": "gulab jamun", "qty": 1, "price": 60}], "coupon": null}order_idcitydishqtypricecouponO1043PuneStep 1: copy the top-level fields.{"order_id": "O1043", "city": "Pune", "items": [ {"dish": "thali", "qty": 1, "price": 220}, {"dish": "lassi", "qty": 2, "price": 100}, {"dish": "gulab jamun", "qty": 1, "price": 60}], "coupon": null}order_idcitydishqtypricecouponO1043Punethali1220nullO1043Punelassi2100nullO1043Punegulab jamun160nullStep 2: explode the items list: one row per item.Order fields repeat on every row.{"order_id": "O1043", "city": "Pune", "items": [ {"dish": "thali", "qty": 1, "price": 220}, {"dish": "lassi", "qty": 2, "price": 100}, {"dish": "gulab jamun", "qty": 1, "price": 60}], "coupon": null}order_idcitydishqtypricecouponO1043Punethali1220nullO1043Punelassi2100nullO1043Punegulab jamun160nullStep 3: missing or null values become NULL.Never invent a value that wasn’t sent.order_idcitydishqtypricecouponO1043Punethali1220nullO1043Punelassi2100nullO1043Punegulab jamun160nullOrder total = 1×220 + 2×100 + 1×60 = ₹480Sum qty × price per order_id. Don’t add a repeated order total per row!Now SQL works: revenue per dish, per city, per day.The raw JSON is still kept in the lake for reprocessing. myphatshala.in
Real-time scenario 2

Outage night: turning 1,000 chats a minute into numbers

At 20:14 support chats explode. Nobody can read a thousand messages a minute. Watch unstructured text become a structured count that tells engineers exactly what broke.

Unstructured → structured, live 5 steps
Animated example money gone!where is my food??paid twice?UPI debitedorder not placedrefund when?payment stuckapp says failed20:14 · chats jump from 60 to 1,000 a minute.Free text, many languages, typos. No one can read them all.Unstructured data, arriving fast.“paisa kat gaya,order nahi aaya”(Hinglish)topic: payment failedlanguage: hi-enconfidence: 0.94A language model labels each chat with a topic.Unstructured text becomes a structured row.chats per minute by topicpayment failed620late delivery140wrong item60refund status120other60Count labelled chats per minute: payment failures dominate.chats per minute by topicpayment failed620late delivery140wrong item60refund status120other60Alert: payment-failed chats are 10× normal.Engineers check the payment gateway: one bank is timing out.chats per minute by topicpayment failed90late delivery150wrong item60refund status260other60Gateway switched; failures fall. Refund questions rise next,so the app shows an automatic “refund in 2 hours” banner. myphatshala.in
Real-time scenario 3 · advanced

Schema drift: the app update that broke the dashboard

Semi-structured data is flexible, which is also its danger. Watch a small app change silently break a pipeline, and the three defences real teams use.

When the shape changes 5 steps
Animated example App 5.1 event"amount": 420"city": "Pune""items": [...]App 5.2 event (new)"amount": "₹420""city_name": "Pune""items": [...]Tuesday: an app update silently changes two fields.amount is now text with ₹, and city was renamed.App 5.1 event"amount": 420"city": "Pune""items": [...]App 5.2 event (new)"amount": "₹420""city_name": "Pune""items": [...]Pipeline: int("₹420") → error · city → NULL40% of orders dropped or missing a cityWednesday 9 am: revenue on the dashboard is down 40%. Panic.Schema check before loadingexpected: amount = number · city = text (required)found: amount = text · city missing · city_name newFix 1: compare each batch with the expected schema.Drift is caught in minutes, before the dashboard updates.Good rows→ silver tableBad rows→ quarantine tableNothing is lost, nothing wrong is published.Fix 2: quarantine rows that fail, keep the rest flowing.An alert goes to the team that owns the app.Data contract: orders_event v2amount: number (paise) · requiredcity: text · required · renamed only with noticenew fields: allowed, optionalFix 3: a data contract agreed by app and data teams,checked in CI before an app release ships. myphatshala.in

Where each shape shows up at work

1. Banks: structured core, unstructured edges

Account and transaction tables are strictly structured. Around them sit cheque images, KYC documents (scanned Aadhaar and PAN copies), call-centre recordings and emails, which are processed with OCR and speech-to-text before analysis.

Cheque image→OCR → fields→Transactions table
{ }clickstream

2. Shopping apps: oceans of JSON events

Every view, search, add-to-cart and checkout is a JSON event with nested product lists. They are streamed through Kafka, stored raw in the lake, and flattened into tables for funnel and recommendation analysis.

App events→Flatten in Spark→Funnel tables
GET /pay 500GET /menu 200

3. Every tech company: logs for monitoring

Servers write millions of log lines an hour. They are parsed into fields (time, service, status code, latency) and searched in tools like Elasticsearch/OpenSearch or a log platform to find errors in seconds.

Log lines→Parse + index→Error dashboard

4. Hospitals and insurance: documents and images

Discharge summaries, prescriptions, lab reports and X-rays are unstructured. Insurers extract fields from claim documents to speed up approvals, while keeping the originals for audit.

Claim PDFs→Extract fields→Approval queue
7 Run the code

Sort records by shape, and read each one

Four records arrive at a data platform. The program decides the shape of each and prints a short summary. Then try the challenges: flatten nested JSON, parse a log line, label free text and catch schema drift.

import json

records = [
    ("table_row", ("O1043", "Pune", 420)),
    ("json", '{"order_id": "O1043", "items": [{"dish": "thali"}, {"dish": "lassi"}]}'),
    ("log", "13:02:11 rider=R77 status=PICKED"),
    ("text", "Thali was great but the lassi was warm"),
]

for kind, value in records:
    if kind == "table_row":
        print("STRUCTURED     :", value, "-> 3 fixed columns")
    elif kind == "json":
        event = json.loads(value)
        print("SEMI-STRUCTURED:", list(event), "-> keys inside the record,", len(event["items"]), "nested items")
    elif kind == "log":
        fields = dict(p.split("=") for p in value.split()[1:])
        print("SEMI-STRUCTURED:", fields, "-> parsed from a pattern")
    else:
        print("UNSTRUCTURED   :", len(value.split()), "words, no fields until we extract some")
STRUCTURED : ('O1043', 'Pune', 420) -> 3 fixed columns SEMI-STRUCTURED: ['order_id', 'items'] -> keys inside the record, 2 nested items SEMI-STRUCTURED: {'rider': 'R77', 'status': 'PICKED'} -> parsed from a pattern UNSTRUCTURED : 8 words, no fields until we extract some
  • event = json.loads(value)json.loads turns JSON text into Python dictionaries and lists. The field names come from the data itself.
  • fields = dict(p.split("=") for p in value.split()[1:])Split the line by spaces, skip the time, then split each key=value pair. This is log parsing.
  • print("UNSTRUCTURED :", len(value.split()), "words, no fields until we extract some")Plain text has no fields. All we can do without extra work is count words.

Run it on your PC: create and read all three shapes

This program creates a sample_data folder with a CSV (structured), a JSON-lines file and a log file (semi-structured) and a text file of reviews (unstructured). Then it reads each one the right way: csv, json, a regular expression and simple text analysis. Only the Python standard library is used. Save it as three_shapes.py in an empty folder and run it, then open the files it creates.

Create and read structured, semi-structured and unstructured files

"""Create one sample file of each shape in a folder, then read each one the right way."""
import csv, json, os, re

os.makedirs("sample_data", exist_ok=True)

# 1. Structured: a CSV with a fixed header
with open("sample_data/orders.csv", "w", newline="") as f:
    w = csv.writer(f)
    w.writerow(["order_id", "city", "amount"])
    w.writerows([["O1", "Pune", 420], ["O2", "Mumbai", 250], ["O3", "Pune", 180]])

# 2. Semi-structured: JSON lines with nesting and a missing key
events = [
    {"order_id": "O1", "items": [{"dish": "thali", "qty": 1}, {"dish": "lassi", "qty": 2}]},
    {"order_id": "O2", "items": [{"dish": "dosa", "qty": 1}], "coupon": "LUNCH20"},
]
with open("sample_data/events.jsonl", "w") as f:
    for e in events:
        f.write(json.dumps(e) + "\n")

# 3. Semi-structured: log lines with a pattern
with open("sample_data/app.log", "w") as f:
    f.write("2026-10-19 13:02:11 INFO  order=O1 status=PLACED ms=120\n")
    f.write("2026-10-19 13:02:15 ERROR order=O2 status=PAY_FAILED ms=3020\n")
    f.write("2026-10-19 13:02:19 INFO  order=O3 status=PLACED ms=95\n")

# 4. Unstructured: free-text reviews
with open("sample_data/reviews.txt", "w") as f:
    f.write("Loved the thali, will order again!\n")
    f.write("Food was cold and delivery very late.\n")

print("Files created:", sorted(os.listdir("sample_data")))

# ---- read each one the right way
with open("sample_data/orders.csv") as f:
    rows = list(csv.DictReader(f))
total = sum(int(r["amount"]) for r in rows)
print(f"\n[structured] {len(rows)} rows, columns {list(rows[0])}, total Rs {total}")

flat = []
with open("sample_data/events.jsonl") as f:
    for line in f:
        e = json.loads(line)
        for item in e["items"]:
            flat.append((e["order_id"], item["dish"], item["qty"], e.get("coupon")))
print(f"[semi-structured JSON] flattened into {len(flat)} item rows:")
for r in flat:
    print("   ", r)

pattern = re.compile(r"(\S+ \S+) (\w+)\s+order=(\w+) status=(\w+) ms=(\d+)")
with open("sample_data/app.log") as f:
    parsed = [pattern.match(line).groups() for line in f]
errors = [p for p in parsed if p[1] == "ERROR"]
print(f"[semi-structured log] {len(parsed)} lines parsed, {len(errors)} error: {errors[0][2]} {errors[0][3]}")

negative = {"cold", "late", "spilled", "wrong"}
with open("sample_data/reviews.txt") as f:
    for line in f:
        words = set(re.findall(r"[a-z]+", line.lower()))
        label = "complaint" if words & negative else "praise"
        print(f"[unstructured] {label:9} <- {line.strip()}")

# In a terminal:  python three_shapes.py
Files created: ['app.log', 'events.jsonl', 'orders.csv', 'reviews.txt'] [structured] 3 rows, columns ['order_id', 'city', 'amount'], total Rs 850 [semi-structured JSON] flattened into 3 item rows: ('O1', 'thali', 1, None) ('O1', 'lassi', 2, None) ('O2', 'dosa', 1, 'LUNCH20') [semi-structured log] 3 lines parsed, 1 error: O2 PAY_FAILED [unstructured] praise <- Loved the thali, will order again! [unstructured] complaint <- Food was cold and delivery very late.

Recorded from a real run with Python 3.13 (any Python 3.8+ gives the same output). Notice that the JSON flattening turned 2 events into 3 item rows and filled the missing coupon with None, the regular expression pulled five fields out of each log line, and the reviews became a structured label.

8 Line by line

Flattening a nested order, one line at a time

An order with two items becomes two flat rows. Watch the inner loop run once per item and the rows list grow, with the order id repeated on each row.

Worked examples: real problems, solved and traced in memory

Four tasks a data engineer does with mixed data, from sorting files by shape to catching schema drift. Each runs live and is traced.

Example 1BeginnerData platform · intake

Sort a folder by shape of data

Problem statement

A shared folder has files from many teams. Count how many are structured, semi-structured and unstructured, based on the extension.

Input
sales.csv, events.json, app.log, cctv.mp4, invoice.pdf, stock.parquet, review.txt, config.xml
Expected output
{'structured': 2, 'semi-structured': 3, 'unstructured': 3}

Approach

  1. Map each known extension to a shape.
  2. Take the text after the last dot of each file name.
  3. Count with a dictionary; unknown extensions count as unstructured.

Solution: run it live

What happens in memory, step by step

Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.

Example 2IntermediateData engineering · flattening

Turn nested JSON orders into item rows

Problem statement

Each order is JSON with a list of items. Produce one row per item with order id, city, dish and line total, then print revenue per dish.

Input
2 orders with 3 items in total
Expected output
{'thali': 660, 'lassi': 200}

Approach

  1. Parse each JSON string with json.loads.
  2. Loop over the nested items and build one row per item.
  3. Group the rows by dish and add the line totals.

Solution: run it live

What happens in memory, step by step

Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.

Example 3IntermediateOperations · logs

Error rate per minute from server logs

Problem statement

Parse log lines into minute and level, then print how many requests and errors happened in each minute and the error percentage.

Input
6 log lines over 2 minutes
Expected output
13:02 3 requests, 33 % errors
13:03 3 requests, 67 % errors

Approach

  1. Split each line: the first part is the time, the second the level.
  2. Use the first five characters of the time (HH:MM) as the minute.
  3. Count totals and errors per minute in two dictionaries.

Solution: run it live

What happens in memory, step by step

Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.

Example 4AdvancedReliability · schema drift

Validate a batch of events against a schema

Problem statement

A batch of JSON events must match the expected schema. Send each event to accept or quarantine, and summarise the problems found.

Input
expected: order_id (str), amount (int), city (str) · 4 events
Expected output
Accepted   : ['O1', 'O4']
Quarantined: ['O2', 'O3']
Problems   : {'bad type amount': 1, 'missing city': 1}

Approach

  1. For each event, find missing keys and keys with the wrong type.
  2. If any problem exists, quarantine the event and record why.
  3. Count each kind of problem across the batch.

Solution: run it live

What happens in memory, step by step

Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.

9 Quiz and revision

Check yourself

Quick revision

Lesson 2.1 in one screen

  • Structured: fixed schema, rows and columns (SQL tables, CSV). Easiest to query.
  • Semi-structured: labels inside each record, flexible and nested (JSON, XML, logs, Avro). Parse and flatten.
  • Unstructured: no data model (text, images, audio, video, PDFs). Add metadata and extract features.
  • Flatten / explode nested lists into one row per item; never sum a repeated parent value.
  • Logs are parsed with split or regular expressions; text becomes labels with keywords or language models.
  • Schema drift = fields change silently. Defend with schema checks, quarantine tables and data contracts.
  • Warehouses store JSON natively (VARIANT, JSON, STRUCT/ARRAY); AI search uses embeddings of unstructured data.

Interview and viva questions

Differentiate structured, semi-structured and unstructured data with examples.

Structured data follows a fixed schema of rows and columns with defined types, for example a bank transactions table. Semi-structured data has tags or keys that describe fields but no fixed table shape, and records can be nested or vary, for example JSON app events, XML files or server logs. Unstructured data has no predefined model, for example images, videos, audio recordings, scanned PDFs and free-text reviews. Structured data is queried directly with SQL; semi-structured data is parsed and flattened; unstructured data needs feature extraction (OCR, speech-to-text, NLP, image models) before analysis.

Is a CSV file always structured?

Usually yes, when it has a consistent header and every row has the same columns and types. A CSV with inconsistent columns, embedded free text or mixed types behaves more like semi-structured data and needs validation before loading.

Why is semi-structured data popular in big data?

It is flexible: producers can add fields without changing a central schema, nested data maps naturally to real objects (an order with items), and formats like JSON are easy for applications to produce. The trade-off is that consumers must handle missing, new and changed fields.

How do you process unstructured data at scale?

Store the raw files in object storage, record metadata for each file, then run extraction jobs in parallel (for example with Spark or a cloud service): OCR for documents, speech-to-text for audio, image classification for photos and language models for text. Save the extracted fields as structured tables linked by ids, and keep the originals for reprocessing.

What is schema drift and how do you handle it?

Schema drift is an unannounced change in the structure of incoming data, such as a renamed field, a new field or a changed type. Handle it with schema validation on every batch, quarantine tables for failing records, alerts on row counts and nulls, parsers that accept known versions, and versioned data contracts agreed with the producing team.

Practice problems

Exercises on reading, flattening and validating mixed data. The last two are business tasks.

P1Easy

Count JSON keys

Print the keys of a JSON order and how many there are.

Input
{"id": "O1", "city": "Pune", "amount": 420}
Expected output
['id', 'city', 'amount'] 3
Show solution and explanation
import json
o = json.loads('{"id": "O1", "city": "Pune", "amount": 420}')
print(list(o), len(o))
['id', 'city', 'amount'] 3

How it works: json.loads returns a dictionary; list(o) gives its keys.

P2Easy

Shape of each value

For each value, print “structured row” for a tuple, “semi-structured” for a dict and “unstructured” for a string.

Input
(1, "Pune"), {"a": 1}, "nice food"
Expected output
(1, 'Pune') -> structured row
{'a': 1} -> semi-structured
nice food -> unstructured
Show solution and explanation
for v in [(1, "Pune"), {"a": 1}, "nice food"]:
    if isinstance(v, tuple):
        print(v, "-> structured row")
    elif isinstance(v, dict):
        print(v, "-> semi-structured")
    else:
        print(v, "-> unstructured")
(1, 'Pune') -> structured row {'a': 1} -> semi-structured nice food -> unstructured

How it works: isinstance checks the type of each value.

P3Easy

Read a CSV text

Read the CSV text and print the total amount.

Input
id,amount / O1,120 / O2,80
Expected output
Total: 200
Show solution and explanation
import csv, io
text = "id,amount\nO1,120\nO2,80\n"
rows = list(csv.DictReader(io.StringIO(text)))
print("Total:", sum(int(r["amount"]) for r in rows))
Total: 200

How it works: csv.DictReader uses the header row as keys; values arrive as text, so convert with int.

P4Medium

Flatten items

Print one line per item: order id, dish, qty.

Input
{"id": "O5", "items": [{"dish": "dosa", "qty": 2}, {"dish": "chai", "qty": 1}]}
Expected output
O5 dosa 2
O5 chai 1
Show solution and explanation
order = {"id": "O5", "items": [{"dish": "dosa", "qty": 2}, {"dish": "chai", "qty": 1}]}
for it in order["items"]:
    print(order["id"], it["dish"], it["qty"])
O5 dosa 2 O5 chai 1

How it works: Loop over the nested list and repeat the parent id on each line.

P5Medium

Parse key=value logs

Turn each log line into a dictionary and print the riders who picked orders.

Input
3 lines
Expected output
['R1', 'R3']
Show solution and explanation
lines = ["rider=R1 status=PICKED", "rider=R2 status=WAITING", "rider=R3 status=PICKED"]
picked = []
for ln in lines:
    f = dict(p.split("=") for p in ln.split())
    if f["status"] == "PICKED":
        picked.append(f["rider"])
print(picked)
['R1', 'R3']

How it works: Each key=value pair becomes a dictionary entry.

P6Advanced

Keys across versions

Events from two app versions have different keys. Print keys common to both, keys only in v1 and keys only in v2.

Input
v1: id, amount, city · v2: id, amount, city_name, coupon
Expected output
Common : ['amount', 'id']
Only v1: ['city']
Only v2: ['city_name', 'coupon']
Show solution and explanation
v1 = {"id", "amount", "city"}
v2 = {"id", "amount", "city_name", "coupon"}
print("Common :", sorted(v1 & v2))
print("Only v1:", sorted(v1 - v2))
print("Only v2:", sorted(v2 - v1))
Common : ['amount', 'id'] Only v1: ['city'] Only v2: ['city_name', 'coupon']

How it works: Set operations quickly show renamed (city → city_name) and new (coupon) fields.

P7Advanced

Null rate per field

For each field, print the percentage of records where it is missing or None.

Input
4 records
Expected output
id      0% missing
city    50% missing
coupon  75% missing
Show solution and explanation
records = [{"id": 1, "city": "Pune", "coupon": None}, {"id": 2, "city": None},
           {"id": 3, "city": "Thane", "coupon": "X"}, {"id": 4}]
fields = ["id", "city", "coupon"]
for f in fields:
    missing = sum(1 for r in records if r.get(f) is None)
    print(f"{f:7} {missing / len(records) * 100:.0f}% missing")
id 0% missing city 50% missing coupon 75% missing

How it works: A sudden jump in a null rate is often the first sign of schema drift.

P8Business problem

Storage by shape

A company stores 2 TB of tables, 30 TB of JSON logs and 400 TB of images and video. Print the percentage of each.

Input
2, 30, 400 TB
Expected output
structured         0.5%
semi-structured    6.9%
unstructured      92.6%
Show solution and explanation
sizes = {"structured": 2, "semi-structured": 30, "unstructured": 400}
total = sum(sizes.values())
for k, v in sizes.items():
    print(f"{k:16} {v / total * 100:5.1f}%")
structured 0.5% semi-structured 6.9% unstructured 92.6%

How it works: Unstructured data dominates storage, even when most reports come from the small structured share.

P9Business problem

Complaint share

Out of reviews labelled by a model, print the share of each complaint type among complaints only.

Input
late 420, cold 180, wrong item 90, praise 1310
Expected output
late       61%
cold       26%
wrong item 13%
Show solution and explanation
labels = {"late": 420, "cold": 180, "wrong item": 90, "praise": 1310}
complaints = {k: v for k, v in labels.items() if k != "praise"}
total = sum(complaints.values())
for k, v in sorted(complaints.items(), key=lambda x: -x[1]):
    print(f"{k:10} {v / total * 100:.0f}%")
late 61% cold 26% wrong item 13%

How it works: Text has become numbers a manager can act on: most complaints are about late delivery.

Real-world scenario questions (asked in company interviews)

Situations with mixed data that come up in real teams and interviews. Decide your answer first, then open the model answer.

Data engineering · ingestionFresher

Load a JSON API into the warehouse

A partner sends orders through an API as JSON. Each order has a nested list of items and an optional coupon object. Your manager wants revenue per dish in the warehouse by tomorrow.

The interviewer is checking: do you know how to land, flatten and validate semi-structured data?

Show a strong answer
  1. Land the raw JSON as-is in the lake (bronze) so you can always reprocess.
  2. Define the target tables: orders (one row per order) and order_items (one row per item, with order_id).
  3. Explode items into order_items; keep the coupon fields as nullable columns.
  4. Validate types and required fields; send failing rows to a quarantine table.
  5. Compute revenue from order_items (qty × price), not from a repeated order total.

Avoid: flattening straight into one wide table and summing a repeated order-level amount.

Analytics · unstructured1–3 years

What are customers complaining about?

You have 2 lakh free-text reviews from the last month in English, Hindi and Hinglish. The product head wants the top five complaint themes by city.

The interviewer is checking: can you turn unstructured text into a structured, trustworthy result?

Show a strong answer
  • Store the raw reviews with order id, city and date (metadata).
  • Clean the text (lower-case, remove emojis or map them, normalise common spellings).
  • Classify each review into themes with a language model or a trained classifier; record the label and a confidence score.
  • Check a random sample by hand to measure accuracy before trusting the numbers.
  • Aggregate counts per theme per city, and show example reviews behind each number.

Avoid: counting single keywords like “bad” and presenting it as insight without checking accuracy.

Reliability · schema drift1–3 years

Revenue dropped 40% overnight

The morning dashboard shows revenue down 40%, but the business team says orders were normal. An app release went out yesterday.

The interviewer is checking: do you suspect the data before the business, and know how to prevent it next time?

Show a strong answer

First, compare raw event counts with orders in the database: if raw counts are normal, the pipeline lost data. Check rejected and quarantined rows and diff the event schema before and after the release; you are likely to find a renamed or retyped field. Fix the parser to handle both versions, backfill the affected day from the raw zone, and publish a corrected dashboard. To prevent repeats: schema checks on every batch, quarantine instead of silent drops, alerts on row-count changes, and a versioned data contract checked before app releases.

Avoid: telling leadership that revenue fell, or patching the dashboard by hand.

Design · storage choiceFresher

Where should each kind of data live?

A new ed-tech start-up has student records, quiz-attempt events from the app, lecture videos and doubt-chat messages. They ask you where to store each.

The interviewer is checking: can you match each shape to sensible storage?

Show a strong answer
  • Student records (structured, updated often) → a relational database such as PostgreSQL.
  • Quiz events (semi-structured, high volume) → a stream (Kafka) into the data lake as JSON, then flattened Parquet tables for analysis.
  • Lecture videos (unstructured, huge) → object storage plus a CDN for streaming; a metadata table for titles, durations and tags.
  • Doubt chats (unstructured text) → stored raw, labelled by topic, and searchable through a search engine.

Avoid: putting videos inside database tables, or forcing every event into a fixed table schema on day one.