Skip to content

JSON, CSV & pickle Interview Questions & Answers

15 questions Updated 2026-06-18 Share:

Python interview questions on json.dumps/loads and type mapping, custom JSON encoding, csv reader vs DictReader, pickle vs json and pickle security, and serializing datetimes.

Read the in-depth guidePython Serialization Explained — JSON, CSV, and pickle for Saving and Exchanging Data(opens in new tab)
15 of 15

json.dumps serializes a Python object to a JSON string; json.loads parses a JSON string back into Python objects. The dump/load variants (no s) work with file objects instead. The conversion follows a fixed type mapping.

import json

data = {"name": "Ada", "age": 36, "tags": ["math"], "active": True}
s = json.dumps(data)            # dict -> JSON string
back = json.loads(s)            # JSON string -> dict

with open("out.json", "w") as f:
    json.dump(data, f)          # write to file

The mapping: dict->object, list/tuple->array, str->string, int/float ->number, True/False->true/false, None->null. Note tuples become arrays (you get a list back), and dict keys are coerced to strings. JSON has no native date, set, or bytes type.

Unsupported types raise TypeError: ... is not JSON serializable. The two standard fixes are the default= callback (a function called for any unserializable object, returning a JSON-friendly substitute) or a custom JSONEncoder subclass passed via cls=.

import json
from datetime import datetime

def encode(obj):
    if isinstance(obj, datetime):
        return obj.isoformat()        # turn it into a string
    raise TypeError(f"not serializable: {type(obj)}")

json.dumps({"when": datetime.now()}, default=encode)

class MyEncoder(json.JSONEncoder):    # the class-based alternative
    def default(self, obj):
        if isinstance(obj, set):
            return list(obj)
        return super().default(obj)
json.dumps({1, 2, 3}, cls=MyEncoder)

Use default= for a quick one-off; subclass JSONEncoder when you want reusable encoding logic. On the way back, use object_hook in loads to reconstruct custom types.

Both read CSV files, but the row format differs. csv.reader yields each row as a list of strings indexed by position. csv.DictReader treats the first row as headers and yields each row as a dict keyed by column name — far more readable and robust to column reordering.

import csv

with open("people.csv", newline="") as f:
    for row in csv.reader(f):
        print(row[0], row[1])         # positional — brittle

with open("people.csv", newline="") as f:
    for row in csv.DictReader(f):
        print(row["name"], row["age"])  # by header — clear

Always open CSV files with newline="" to let the csv module handle line endings correctly. Prefer DictReader/DictWriter for named-column access; use the plain reader for headerless or purely positional data.

json is a text, language-independent format for simple data (dicts, lists, numbers, strings). pickle is a binary, Python-specific format that can serialize almost any Python object (custom classes, functions references, nested objects). The crucial caveat: never unpickle untrusted data.

import json, pickle

json.dumps({"x": 1})              # "{\"x\": 1}" — readable, portable
pickle.dumps({"x": 1})            # b'\x80\x04...' — binary, Python-only

# DANGER: unpickling runs arbitrary code embedded in the data
pickle.loads(untrusted_bytes)     # can execute malicious payloads!

pickle.loads can execute arbitrary code during deserialization, so it's a remote-code-execution risk on attacker-controlled input. Rule of thumb: use json for config, APIs, and anything crossing a trust boundary; use pickle only for trusted, internal Python-to-Python data (e.g. caches you wrote yourself).

JSON has no date type, so a datetime must be converted to a string — the ISO 8601 format via .isoformat() is the standard choice because it's unambiguous and parseable. On the way out use default=; on the way back parse the string with datetime.fromisoformat().

import json
from datetime import datetime

event = {"name": "launch", "at": datetime(2026, 6, 18, 14, 30)}

text = json.dumps(event, default=lambda o: o.isoformat())
# '{"name": "launch", "at": "2026-06-18T14:30:00"}'

raw = json.loads(text)
raw["at"] = datetime.fromisoformat(raw["at"])   # back to datetime

JSON can't tell a date-string from an ordinary string, so you must know which fields to re-parse (or use object_hook). Prefer ISO strings in UTC for portability, and convert to local time only at display.

json.dumps accepts indent (pretty-print), sort_keys (deterministic key order), separators (compact output), and ensure_ascii (escape non-ASCII or keep Unicode). These shape readability and size.

import json
data = {"b": 1, "a": [1, 2]}
json.dumps(data, indent=2)                    # pretty, multi-line
json.dumps(data, sort_keys=True)              # '{"a": [1, 2], "b": 1}'
json.dumps(data, separators=(",", ":"))       # '{"b":1,"a":[1,2]}' — compact
json.dumps({"x": "café"}, ensure_ascii=False) # keeps 'café' (not \u...)

Rule of thumb: indent for human-readable files/logs, compact separators for network payloads, sort_keys for reproducible output (e.g. diffs/hashing).

json.loads(..., object_hook=fn) calls fn on every decoded object (dict), letting you transform it into a richer Python type — the inverse of default= for encoding.

import json
from datetime import datetime

def decode(d):
    if "at" in d:
        d["at"] = datetime.fromisoformat(d["at"])
    return d

json.loads('{"at": "2026-06-18T14:30:00"}', object_hook=decode)
# {'at': datetime(2026, 6, 18, 14, 30)}

Rule of thumb: pair default= (encode) with object_hook (decode) to round-trip custom types like datetimes, Decimals, or your own classes through JSON.

Use csv.writer (lists) or csv.DictWriter (dicts, with writeheader()). Open with newline="" to prevent blank rows on Windows, and let the module handle quoting of fields containing commas/quotes/newlines.

import csv
with open("out.csv", "w", newline="") as f:
    w = csv.DictWriter(f, fieldnames=["name", "age"])
    w.writeheader()
    w.writerow({"name": "Ada, the", "age": 36})   # comma auto-quoted
    w.writerows([{"name": "Bob", "age": 40}])

Rule of thumb: always newline="" when opening CSV files, and use DictWriter with writeheader() for named columns — never hand-build CSV with string joins (quoting!).

Python's json emits NaN, Infinity, -Infinity by default (which is invalid JSON); pass allow_nan=False to raise instead. Large ints serialize fine but may lose precision when read by JavaScript (its numbers are 64-bit floats).

import json
json.dumps(float("nan"))                 # 'NaN' — not valid standard JSON
json.dumps(float("inf"), allow_nan=False)  # ValueError

json.dumps(2**60)                        # fine in Python
# but JS parsing loses precision beyond 2**53

Rule of thumb: set allow_nan=False for strict JSON; serialize very large integers as strings if a JavaScript/other consumer must read them safely.

Pickle has versioned protocols (higher = more efficient/compact; protocol 5 is latest). Some objects can't be pickled: open file handles, sockets, lambdas, and most local/nested functions. __reduce__/__getstate__ customize pickling.

import pickle
pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL)

pickle.dumps(lambda x: x)        # PicklingError — lambdas can't be pickled
pickle.dumps(open("f"))          # TypeError — file handles can't be pickled

Rule of thumb: use HIGHEST_PROTOCOL for internal pickling; for things pickle can't handle (lambdas, connections), redesign or implement __getstate__/__setstate__.

For untrusted or cross-language data, prefer JSON (simple data), json/CSV for tabular, or schema-based formats like Protocol Buffers, MessagePack, or TOML/YAML for config. These don't execute code on load.

import json
# safe round-trip for plain data:
json.dump(config, open("config.json", "w"))

# for binary efficiency without code execution: msgpack (third-party)
# for typed cross-language schemas: protobuf, avro

Rule of thumb: reach for pickle only for trusted internal Python state; use JSON/ msgpack/protobuf at trust boundaries and for interoperability.

JSON object keys must be strings, so json.dumps coerces int/float/bool/ None keys to strings — and on loads they come back as strings, not their original type. Other key types (tuples) raise TypeError.

import json
s = json.dumps({1: "a", True: "b"})
s                       # '{"1": "a", "true": "b"}' — keys stringified
json.loads(s)           # {'1': 'a', 'true': 'b'} — keys are now strings

json.dumps({(1, 2): "x"})   # TypeError — tuple keys not allowed

Rule of thumb: JSON keys round-trip as strings — if you need int keys back, convert them yourself after loading, and avoid non-stringifiable key types.

Pass delimiter=, quotechar=, and quoting= to reader/writer, or use a registered dialect. This handles TSV, semicolon-separated, or quote-everything formats.

import csv
# tab-separated:
csv.reader(f, delimiter="\t")
# semicolon with custom quote:
csv.writer(f, delimiter=";", quotechar="'", quoting=csv.QUOTE_ALL)

csv.register_dialect("pipes", delimiter="|")
csv.reader(f, dialect="pipes")

Rule of thumb: configure delimiter/quoting for non-CSV-comma formats; the csv module handles embedded delimiters/quotes correctly so you don't have to.

json.load reads the whole document into memory. For huge files, use line-delimited JSON (one object per line) and process line by line, or a streaming/incremental parser like the third-party ijson.

import json
# JSON Lines: one JSON object per line — stream it
with open("events.jsonl") as f:
    for line in f:
        obj = json.loads(line)     # constant memory per record
        process(obj)

# for a single giant nested JSON, use ijson (incremental) instead of json.load

Rule of thumb: prefer JSON Lines for large datasets so you can stream records; reach for ijson when you must incrementally parse one massive JSON document.

Convert with dataclasses.asdict() to a plain dict for json.dumps, and reconstruct by unpacking the loaded dict into the dataclass (Cls(**data)). Nested dataclasses need recursive handling or a library like pydantic.

import json
from dataclasses import dataclass, asdict

@dataclass
class User:
    name: str
    age: int

u = User("Ada", 36)
s = json.dumps(asdict(u))        # '{"name": "Ada", "age": 36}'
User(**json.loads(s))            # User(name='Ada', age=36)

Rule of thumb: asdict out, Cls(**d) in for flat dataclasses; for nested or validated models, use pydantic/dataclasses-json rather than hand-rolling.

More ways to practice

The self-quiz is live. Join our channel for updates, new content & tech tips.

Join our WhatsApp Channel