{"course":{"slug":"building-apps-with-ai","title":"Building Apps with AI","url":"https://addaly.com/learn/building-apps-with-ai","level":"beginner","track":"build"},"lesson":{"slug":"examples-beat-descriptions","title":"Examples beat descriptions","url":"https://addaly.com/learn/building-apps-with-ai/examples-beat-descriptions","position":16,"of":87,"minutes":8,"answer":"Point at an existing file, paste three messy real rows, and write the exact output you expect: every ambiguity resolved by an example is one the model cannot invent an answer to.","keyIdea":"Point at an existing file, paste three messy real rows, and write the exact output you expect: every ambiguity resolved by an example is one the model cannot invent an answer to."},"body":{"format":"markdown","text":"## Point at something that already exists\n\nThe strongest instruction you can give is not a better adjective. It is a pointer.\n\n```\nAdd a cancel button to BookingRow.tsx that matches the edit\nbutton already in that file — same size, same variant, same\nconfirmation dialog pattern as DeleteCustomerButton.tsx.\n```\n\nEvery ambiguity in that request is resolved by something on disk. You get consistency without describing it, and the model does not have to guess your house style from the average of everybody's.\n\nThis is the single highest-return habit in prompting for code, and it is underused because it feels like less work than writing a paragraph. It is less work. That is the point.\n\n## Sample data resolves more than prose\n\nThree real rows tell a model things you would never think to mention.\n\n```\nid | customer_name | phone       | starts_at            | status\n71 | Anita Rao     | 09876543210 | 2026-09-11T14:00:00Z | confirmed\n72 | S. Krishnan   |             | 2026-09-11T14:30:00Z | confirmed\n73 | Meera D'Souza | +919876543210| 2026-09-12T09:00:00Z | cancelled\n```\n\nFrom that the model learns that phone is sometimes empty, sometimes has a leading zero, sometimes has a country code, so it must be text, not a number. It learns that names contain apostrophes and full stops, so any string handling must survive them. It learns your timestamps are UTC.\n\nInvent the rows if you must, but invent them honestly: include the messy ones. A sample where every field is filled and every name is Alice teaches the model that your data is clean, and it will write code that assumes so.\n\n## Show the output you want\n\nFor anything that produces a format (an API response, a CSV, an email, a summary) write the exact output you expect before asking for the code.\n\n```\nGET /api/bookings/71 should return exactly:\n\n{\n  \"id\": 71,\n  \"customer\": { \"name\": \"Anita Rao\", \"phone\": \"09876543210\" },\n  \"startsAt\": \"2026-09-11T14:00:00Z\",\n  \"durationMinutes\": 30,\n  \"status\": \"confirmed\"\n}\n\nNo other fields. Never include the internal notes column.\n```\n\nThat is a specification and a test in one. Compare it to \"return the booking details\", which returns whatever the model felt like, usually including three fields you did not want to publish.\n\n## Screenshots, with an arrow on them\n\nFor anything visual, a picture ends the discussion. Take the screenshot, open it in any free editor (GIMP, Krita, Paint, the markup tool on your phone) draw a red arrow and three words.\n\n\"The gap under the header is too big on mobile\" plus a screenshot with an arrow gets fixed on the first attempt. The same sentence alone frequently gets the wrong gap adjusted, twice.\n\nThis also works in the other direction: a screenshot of an app you admire, with a note saying *this arrangement, not these colours*, communicates layout intent faster than any description of columns and spacing.\n\n## Counter-examples are cheap and strong\n\nSaying what you do not want narrows the field usefully:\n\n```\nNot a modal. Not a new page. Inline, under the row that was clicked.\nNo animation library. Do not add a date picker component;\nuse the native input.\n```\n\nEach of those lines removes an option the model would otherwise have taken with about a one-in-three chance, and taking it costs you a review cycle and a dependency you did not want.\n\n## The pattern file\n\nOnce your project has one route, one form and one query you are happy with, name them in your rules file as the canonical examples:\n\n```\nWhen adding an API route, follow app/api/bookings/route.ts exactly:\nsame validation, same error shape, same auth check, same ordering.\n```\n\nNow your conventions are enforced by a file rather than by your memory, and the twentieth route looks like the first. That consistency is what makes a generated codebase reviewable: you learn to read one pattern rather than twenty variations of it.\n\n## The limitation worth naming\n\nExamples propagate whatever they contain, including mistakes. If your canonical route has a missing authorisation check, twenty routes now have it. So the file you point at should be one you have actually read, line by line, once. Pointing at a pattern is a multiplier, and multipliers work in both directions."},"check":{"question":"Why include a row with an empty phone number and a name containing an apostrophe in the sample data you give the model?","options":[{"text":"It demonstrates the variety of your customer base, which affects design choices.","correct":false,"tempting":"Reads the sample as market context rather than as evidence about field shape and null handling."},{"text":"Longer samples give the model more context to work from.","correct":false,"tempting":"Values volume over content; three tidy rows are longer than nothing and still teach that the data is clean."},{"text":"It teaches the model that the field is nullable and that strings contain characters which break naive handling, so the code it writes accounts for both.","correct":true},{"text":"It prevents the model from inventing its own sample data during development.","correct":false,"tempting":"Addresses seeding rather than code shape; invented samples are usually harmless while wrong assumptions about nullability are not."}]},"prev":{"slug":"naming-things-so-they-stay-findable","title":"Naming things so you can still find them","url":"https://addaly.com/learn/building-apps-with-ai/naming-things-so-they-stay-findable"},"next":{"slug":"fencing-the-change","title":"Fencing the change so it stays where you put it","url":"https://addaly.com/learn/building-apps-with-ai/fencing-the-change"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Examples beat descriptions', in Building Apps with AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/building-apps-with-ai/examples-beat-descriptions (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Examples beat descriptions","courseTitle":"Building Apps with AI","module":{"index":2,"slug":"saying-what-you-want","title":"Saying what you want, precisely enough"},"answer":null,"keyIdea":"Point at an existing file, paste three messy real rows, and write the exact output you expect: every ambiguity resolved by an example is one the model cannot invent an answer to.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"point-at-something-that-already-exists","runs":[{"text":"Point at something that already exists"}]},{"t":"p","runs":[{"text":"The strongest instruction you can give is not a better adjective. It is a pointer."}]},{"t":"code","lang":"","label":"Text","text":"Add a cancel button to BookingRow.tsx that matches the edit\nbutton already in that file — same size, same variant, same\nconfirmation dialog pattern as DeleteCustomerButton.tsx."},{"t":"p","runs":[{"text":"Every ambiguity in that request is resolved by something on disk. You get consistency without describing it, and the model does not have to guess your house style from the average of everybody’s."}]},{"t":"p","runs":[{"text":"This is the single highest-return habit in prompting for code, and it is underused because it feels like less work than writing a paragraph. It is less work. That is the point."}]},{"t":"h2","id":"sample-data-resolves-more-than-prose","runs":[{"text":"Sample data resolves more than prose"}]},{"t":"p","runs":[{"text":"Three real rows tell a model things you would never think to mention."}]},{"t":"code","lang":"","label":"Text","text":"id | customer_name | phone       | starts_at            | status\n71 | Anita Rao     | 09876543210 | 2026-09-11T14:00:00Z | confirmed\n72 | S. Krishnan   |             | 2026-09-11T14:30:00Z | confirmed\n73 | Meera D'Souza | +919876543210| 2026-09-12T09:00:00Z | cancelled"},{"t":"p","runs":[{"text":"From that the model learns that phone is sometimes empty, sometimes has a leading zero, sometimes has a country code, so it must be text, not a number. It learns that names contain apostrophes and full stops, so any string handling must survive them. It learns your timestamps are UTC."}]},{"t":"p","runs":[{"text":"Invent the rows if you must, but invent them honestly: include the messy ones. A sample where every field is filled and every name is Alice teaches the model that your data is clean, and it will write code that assumes so."}]},{"t":"h2","id":"show-the-output-you-want","runs":[{"text":"Show the output you want"}]},{"t":"p","runs":[{"text":"For anything that produces a format (an API response, a CSV, an email, a summary) write the exact output you expect before asking for the code."}]},{"t":"code","lang":"","label":"Text","text":"GET /api/bookings/71 should return exactly:\n\n{\n  \"id\": 71,\n  \"customer\": { \"name\": \"Anita Rao\", \"phone\": \"09876543210\" },\n  \"startsAt\": \"2026-09-11T14:00:00Z\",\n  \"durationMinutes\": 30,\n  \"status\": \"confirmed\"\n}\n\nNo other fields. Never include the internal notes column."},{"t":"p","runs":[{"text":"That is a specification and a test in one. Compare it to “return the booking details”, which returns whatever the model felt like, usually including three fields you did not want to publish."}]},{"t":"h2","id":"screenshots-with-an-arrow-on-them","runs":[{"text":"Screenshots, with an arrow on them"}]},{"t":"p","runs":[{"text":"For anything visual, a picture ends the discussion. Take the screenshot, open it in any free editor (GIMP, Krita, Paint, the markup tool on your phone) draw a red arrow and three words."}]},{"t":"p","runs":[{"text":"“The gap under the header is too big on mobile” plus a screenshot with an arrow gets fixed on the first attempt. The same sentence alone frequently gets the wrong gap adjusted, twice."}]},{"t":"p","runs":[{"text":"This also works in the other direction: a screenshot of an app you admire, with a note saying "},{"text":"this arrangement, not these colours","i":true},{"text":", communicates layout intent faster than any description of columns and spacing."}]},{"t":"h2","id":"counter-examples-are-cheap-and-strong","runs":[{"text":"Counter-examples are cheap and strong"}]},{"t":"p","runs":[{"text":"Saying what you do not want narrows the field usefully:"}]},{"t":"code","lang":"","label":"Text","text":"Not a modal. Not a new page. Inline, under the row that was clicked.\nNo animation library. Do not add a date picker component;\nuse the native input."},{"t":"p","runs":[{"text":"Each of those lines removes an option the model would otherwise have taken with about a one-in-three chance, and taking it costs you a review cycle and a dependency you did not want."}]},{"t":"h2","id":"the-pattern-file","runs":[{"text":"The pattern file"}]},{"t":"p","runs":[{"text":"Once your project has one route, one form and one query you are happy with, name them in your rules file as the canonical examples:"}]},{"t":"code","lang":"","label":"Text","text":"When adding an API route, follow app/api/bookings/route.ts exactly:\nsame validation, same error shape, same auth check, same ordering."},{"t":"p","runs":[{"text":"Now your conventions are enforced by a file rather than by your memory, and the twentieth route looks like the first. That consistency is what makes a generated codebase reviewable: you learn to read one pattern rather than twenty variations of it."}]},{"t":"h2","id":"the-limitation-worth-naming","runs":[{"text":"The limitation worth naming"}],"role":"limit"},{"t":"p","runs":[{"text":"Examples propagate whatever they contain, including mistakes. If your canonical route has a missing authorisation check, twenty routes now have it. So the file you point at should be one you have actually read, line by line, once. Pointing at a pattern is a multiplier, and multipliers work in both directions."}]}],"check":{"question":[{"text":"Why include a row with an empty phone number and a name containing an apostrophe in the sample data you give the model?"}],"options":[{"text":[{"text":"It demonstrates the variety of your customer base, which affects design choices."}],"correct":false,"tempting":[{"text":"Reads the sample as market context rather than as evidence about field shape and null handling."}]},{"text":[{"text":"Longer samples give the model more context to work from."}],"correct":false,"tempting":[{"text":"Values volume over content; three tidy rows are longer than nothing and still teach that the data is clean."}]},{"text":[{"text":"It teaches the model that the field is nullable and that strings contain characters which break naive handling, so the code it writes accounts for both."}],"correct":true,"tempting":null},{"text":[{"text":"It prevents the model from inventing its own sample data during development."}],"correct":false,"tempting":[{"text":"Addresses seeding rather than code shape; invented samples are usually harmless while wrong assumptions about nullability are not."}]}],"order":[1,0,2,3],"why":null},"prev":{"slug":"naming-things-so-they-stay-findable","title":"Naming things so you can still find them","minutes":8},"next":{"slug":"fencing-the-change","title":"Fencing the change so it stays where you put it","minutes":8}}}