0Pricing
TypeScript Academy · Lesson

Inferring Query Result Shapes

Let the query builder infer the returned row type.

Inferring Query Result Shapes is a free TypeScript Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the TypeScript Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

The Result Type Follows the Selection

A great query builder does not just validate input; it infers the exact shape of each result row from the columns you selected. Select fewer columns and the result type narrows.

Full Row Select

Calling select() with no projection returns the entire row type.

const rows = await db.select().from(users);
// rows: { id: number; name: string; age: number | null; isAdmin: boolean }[]

Partial Select Narrows the Type

Project only the columns you need and the result type contains only those keys.

const rows = await db
  .select({ id: users.id, name: users.name })
  .from(users);
// rows: { id: number; name: string }[]
// Accessing rows[0].age is a compile error.

Renaming Columns in the Result

The keys of the select object become the result keys, so you can rename freely and the type reflects the new names.

const rows = await db
  .select({ userId: users.id, fullName: users.name })
  .from(users);
// rows: { userId: number; fullName: string }[]

Computed and Aggregated Columns

Aggregates carry their own inferred type. count() yields a number; the alias key names it in the result.

import { count } from "drizzle-orm";

const rows = await db
  .select({ total: count() })
  .from(users);
// rows: { total: number }[]

Nullability Flows Through

If you select a nullable column, its nullability is preserved in the result. Selecting age keeps the number | null type.

const rows = await db.select({ age: users.age }).from(users);
// rows: { age: number | null }[]

How the Inference Is Built

The result type is a mapped type over the select object: each value is a column whose data type (and nullability) is read out into the corresponding key.

type InferSelect<S> = {
  [K in keyof S]: S[K] extends Column<infer D, infer Null>
    ? Null extends true ? D | null : D
    : never;
};

Single Row Helpers

Some builders offer a .get() or [0] access. The element type is still derived from the same selection, just unwrapped from the array.

const one = (await db.select({ id: users.id }).from(users))[0];
// one: { id: number } | undefined

Joins Extend the Shape

When you join, the result type gains keys from the joined table. Partial selects across joined tables still narrow precisely. (Joins get their own lesson.)

const rows = await db
  .select({ name: users.name, title: posts.title })
  .from(users)
  .innerJoin(posts, eq(posts.authorId, users.id));
// rows: { name: string; title: string }[]

Why Narrowing Matters

Returning only the keys you selected prevents accidental reliance on columns you did not fetch. The type mirrors the actual SQL, so over-fetching and under-fetching bugs surface in the editor.

Pitfall: Spreading Then Picking

If you fetch the full row and then manually pick keys in JS, you lose the database-level narrowing benefit and ship extra columns over the wire. Prefer narrowing at the query.

Quick Check

Confirm your understanding of inferred result shapes.

Recap

The result type is computed from the selection via a mapped type: full selects return the whole row, partial selects narrow to the chosen keys, renames change result keys, nullability flows through, and aggregates carry their own types. The static type always mirrors the SQL you actually wrote.

Frequently asked questions

Is the “Inferring Query Result Shapes” lesson free?

Yes — the full text of “Inferring Query Result Shapes” is free to read here on the web, and the TypeScript Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the TypeScript Academy course, upgrade to CoddyKit PRO.

What will I learn in “Inferring Query Result Shapes”?

Let the query builder infer the returned row type. You practise TypeScript Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start TypeScript Academy?

No prior experience is required. TypeScript Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Inferring Query Result Shapes” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this TypeScript Academy lesson?

Yes. Every TypeScript Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Schema-to-Type Mapping
  2. Type-Safe Query Construction
  3. Inferring Query Result Shapes
  4. Relations and Joins with Types
← Back to TypeScript Academy