Menu

MLOps course · Lesson 3 of 4

Model Serving: Batch vs Online Predictions

How to serve ML predictions: batch scoring vs online APIs, input validation, latency and versioning, with a runnable batch job and a small prediction API.

  • Intermediate
  • 4 min read
  • Updated Nov 2026
On this page
  1. Batch vs online
  2. Both in one example
  3. Serving checklist
  4. Common mistakes
  5. Key takeaway

A trained model is only useful when its predictions reach the people or systems that act on them. There are two main ways to serve predictions. Batch scores many records on a schedule and stores the results. Online scores one request at a time, on demand, usually through an API. Choosing the right one saves a lot of complexity.

Batch vs online

Batch Online
When predictions are made On a schedule, such as nightly When a request arrives
Speed needed Minutes or hours for the whole run Milliseconds per request
Typical use Churn scores, demand forecasts, lead scoring Fraud checks at payment, recommendations on a page
Complexity Low: a scheduled job writing a table Higher: a service, scaling, fresh features
Uses latest data As of the last run Yes, at request time

Choose batch unless the decision needs data from the last few seconds or minutes. Many teams start with batch and add online serving only for the decisions that need it.

Both in one example

This example scores 100,000 customers in a batch, then serves the same model behind a small HTTP API that validates its input:

import json, threading
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.request import Request, urlopen
import numpy as np
from sklearn.linear_model import LogisticRegression

rng = np.random.default_rng(3)
X = rng.normal(size=(2000, 4)); y = (X @ [0.8, -0.5, 0.3, 0.0] + rng.normal(0, 1, 2000) > 0).astype(int)
model = LogisticRegression().fit(X, y)
FEATURES = ["tenure", "spend", "tickets", "logins"]

# Batch: score every customer once a night and save the results for other systems to read
customers = rng.normal(size=(100_000, 4))
scores = model.predict_proba(customers)[:, 1]
print(f"batch: scored {len(scores):,} customers; "
      f"{(scores > 0.7).sum():,} above 0.7")

# Online: a small HTTP service that scores one customer per request
class Score(BaseHTTPRequestHandler):
    def do_POST(self):
        body = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
        missing = [f for f in FEATURES if f not in body]
        if missing:                                         # validate input before the model sees it
            code, out = 400, {"error": f"missing features: {missing}"}
        else:
            p = model.predict_proba([[body[f] for f in FEATURES]])[0, 1]
            code, out = 200, {"churn_probability": round(float(p), 3), "model_version": "v1"}
        self.send_response(code); self.end_headers(); self.wfile.write(json.dumps(out).encode())
    def log_message(self, *a):
        pass

server = HTTPServer(("127.0.0.1", 0), Score)
threading.Thread(target=server.serve_forever, daemon=True).start()
url = f"http://127.0.0.1:{server.server_port}/score"
def call(payload):
    req = Request(url, data=json.dumps(payload).encode(), headers={"Content-Type": "application/json"})
    try:
        return json.load(urlopen(req))
    except Exception as e:
        return json.load(e)
print("online:", call({"tenure": 1.2, "spend": -0.3, "tickets": 0.5, "logins": 0.1}))
print("online:", call({"tenure": 1.2}))
server.shutdown()
batch: scored 100,000 customers; 31,523 above 0.7
online: {'churn_probability': 0.902, 'model_version': 'v1'}
online: {'error': "missing features: ['spend', 'tickets', 'logins']"}

The batch run scores everyone in one go; the results would be written to a table for the retention team. The online service returns a probability and the model version for a valid request, and rejects an incomplete request with a clear error instead of guessing missing values. Production services use a web framework or a model-serving platform, with authentication, logging and scaling, but the shape is the same.

Serving checklist

  • Same features in training and serving: share the feature code, or use a feature store, to avoid training-serving skew.
  • Validate inputs: types, ranges and missing fields, before the model sees them.
  • Return the model version with every prediction, and log both.
  • Roll out gradually: shadow mode (score without using the result) or a small share of traffic first.
  • Have a fallback: a previous model version or a simple rule if the service fails.
  • Set latency budgets and measure them, including feature lookups.

Common mistakes

  1. Building an online API for a decision that happens once a day.
  2. Recomputing features differently in serving than in training.
  3. No input validation, so bad requests produce confident nonsense.
  4. No version in the response, so nobody knows which model made a prediction.

Key takeaway

Serve predictions in batch when the decision can wait, and online when it needs fresh data within seconds. Whichever you choose, use the same features as training, validate inputs, return the model version, roll out gradually and keep a fallback. Next: model monitoring and drift.

By , Data Engineer and Founder of DataDank · Last reviewed Nov 2026 · Examples run on Python 3 with scikit-learn 1.9, NumPy 2.x and joblib, using only the standard library for the HTTP service; their output is real.

Progress is saved in this browser only. No account needed.

Search
Filter by type