Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
155732 stories
·
33 followers

The hidden pattern behind successful products | Mark Pincus (founder of Zynga)

1 Share

Mark Pincus founded Zynga—the company behind Words With Friends, FarmVille, and Zynga Poker—and has arguably created more hit consumer products than anyone in history. At Zynga, eight of 10 major game launches became massive hits, reaching over a billion players. Over the past five years, Mark has been synthesizing everything he’s learned about building successful consumer products and turning it into a book, Life at the Speed of Play, which comes out on June 23. This is the first interview he’s done about the book.

In our in-depth conversation, we discuss:

1. His “Proven, Better, New” framework: copy what’s proven, make it better so that 10 out of 10 people say “f*ck yes, I’ll use this”—then add something new

2. Why being less ambitious is the path to the most ambitious ideas

3. His rule of thumb that your instincts are right 95% of the time, but your ideas are wrong 75% of the time

4. “Kill hope before hope kills you”

5. How to raise kids in the age of AI

Brought to you by:

WorkOS—Make your app enterprise-ready, with SSO, SCIM, RBAC, and more

Vanta—Automate compliance, manage risk, and accelerate trust with AI

Episode transcript: https://www.lennysnewsletter.com/p/the-common-pattern-behind-successful

Archive of all Lenny's Podcast transcripts: https://www.dropbox.com/scl/fo/yxi4s2w998p1gvtpu4193/AMdNPR8AOw0lMklwtnC0TrQ?rlkey=j06x0nipoti519e0xgm23zsn9&st=ahz0fj11&dl=0

Where to find Mark Pincus:

• X: https://x.com/markpinc

• LinkedIn: https://www.linkedin.com/in/markpincus

• Website: https://www.lifeatthespeedofplay.com

Where to find Lenny:

• Newsletter: https://www.lennysnewsletter.com

• X: https://twitter.com/lennysan

• LinkedIn: https://www.linkedin.com/in/lennyrachitsky/

In this episode, we cover:

(00:00) Introduction to Mark Pincus

(02:46) The Proven Better New framework overview

(07:29) Earning the right to innovate

(08:30) What “better” really means

(12:03) Quick summary of the framework

(12:40) Examples of the framework in action

(13:30) How to use proven correctly on your platform

(15:13) The moral arbitrage of copying

(23:55) Be less ambitious

(28:25) The Bolt.new story and staying humble

(33:15) Kill hope before hope kills you

(37:00) Using AI as a failure machine

(40:08) Why Zynga’s games succeeded (it wasn’t virality)

(48:36) The future of consumer social apps

(57:05) How to know if your product is a B+

(1:01:25) Distribution in the age of AI

(1:15:39) Make everyone a CEO

(1:18:18) Stay close to the metal

(1:21:35) Why Mark says micromanagement is beautiful

(1:23:35) The expert witness

(1:25:05) The number one job of a CEO is to be right

(1:26:35) What Mark is teaching his five kids

(1:35:14) Mark’s “why”

(1:37:08) Mark’s new book: Life at The Speed of Play

Referenced:

Tribe.net: https://en.wikipedia.org/wiki/Tribe.net

• Zynga: https://www.zynga.com

• Sid Meier: https://en.wikipedia.org/wiki/Sid_Meier

• Electronic Arts: https://www.ea.com

• CityVille: https://en.wikipedia.org/wiki/CityVille

• Words With Friends: https://wordswithfriends.com/

• Scrabble: https://playscrabble.com

• Reddit: https://www.reddit.com

• TED Radio Hour, MIT Media Lab founder, 1984 TED talk.: https://www.ted.com/talks/nicholas_negroponte_5_predictions_from_1984

• Peter Thiel on LinkedIn: https://www.linkedin.com/in/peterthiel

• FarmVille: https://en.wikipedia.org/wiki/FarmVille

• Craig Newmark: https://en.wikipedia.org/wiki/Craig_Newmark

• How to consistently go viral: Nikita Bier’s playbook for winning at consumer apps (co-founder of TBH, Gas, advisor, investor): https://www.lennysnewsletter.com/p/how-to-consistently-go-viral-nikita-bier

• Angry Birds: https://www.angrybirds.com/

• OMGPop: https://en.wikipedia.org/wiki/OMGPop

• Draw Something: https://en.wikipedia.org/wiki/Draw_Something

• Slack founder: Mental models for building products people love ft. Stewart Butterfield: https://www.lennysnewsletter.com/p/slack-founder-stewart-butterfield

• Brian Chesky’s new playbook: https://www.lennysnewsletter.com/p/brian-cheskys-contrarian-approach

• Garry Tan on LinkedIn: https://www.linkedin.com/in/garrytan

• Brian Armstrong on LinkedIn: https://www.linkedin.com/in/barmstrong

• Jason Citron on X: https://x.com/jasoncitron

• Stanislav Vishnevskiy on LinkedIn: https://www.linkedin.com/in/svishnevskiy

• Jeff Bezos on X: https://x.com/JeffBezos

• Andy Jassy on X: https://x.com/ajassy

• Niantic: https://nianticlabs.com

• Pokémon Go: https://pokemongo.com

• Bing Gordon on LinkedIn: https://www.linkedin.com/in/binggordon

Recommended book:

Life at the Speed of Play: Launch Products People Love!: https://www.amazon.com/Life-Speed-Play-Launch-Products/dp/0063352575/ref=tmm_hrd_swatch_0

Production and marketing by https://penname.co/. For inquiries about sponsoring the podcast, email podcast@lennyrachitsky.com.

Lenny may be an investor in the companies discussed.



To hear more, visit www.lennysnewsletter.com



Download audio: https://api.substack.com/feed/podcast/198591984/bd6e292d565643f3bebb1afad1a00191.mp3
Read the whole story
alvinashcraft
21 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Android Weekly Issue #731

1 Share
Articles & Tutorials
We reach out to more than 80k Android developers around the world, every week, through our email newsletter and social media channels. Advertise your Android development related service or product!
alt
Learn how Datadog integrated Android 15's ProfilingManager API to surface code-level performance insights from production apps at scale.
KMP Bits demonstrates how to implement shared barcode scanning in Compose Multiplatform using expect/actual at the Composable level with CameraX/MLKit and AVFoundation.
Daniil Chernyaev explores how Kotlin select expressions adapt to changing async data-handling requirements across three evolving task formulations.
Nav Singh walks through implementing Android 17's new Contact Picker, a privacy-first alternative to the READ_CONTACTS permission.
Gabriel Bronzatti Moro walks through migrating a multimodule Compose Multiplatform project from KSP-based Koin Annotations to the new Koin Compiler plugin.
Jaewoong Eum explores the new Stability Doctor and trace-all mode in Compose Stability Analyzer 0.10.0, offering ranked fix prescriptions backed by static and runtime data.
Jaewoong Eum walks through a new Gradle plugin that generates type-safe Kotlin accessors for RevenueCat dashboard entitlements and offerings at build time.
Chris Merrick explains how repeated AI completion failures on Android apps led him to build a multi-agent verification framework with role separation and file-based state.
Daniel Bertoldi compares KMP's two iOS interop approaches — SKIE and Swift Export — by examining their compiled Swift output across real-world scenarios.
Libraries & Code
A Gradle plugin and IDE extension that visualizes your entire Compose app's navigation flow as an interactive, thumbnail-rich map.
alt
An Android Studio plugin that integrates LeakCanary's heap analysis engine with AI-powered fix suggestions directly inside the IDE.
A KMP JSON Pointer library adds unofficial-but-popular dot notation support alongside standard JSON Pointer and URI fragment identifier syntaxes.
A Ktor plugin that converts HTTP client requests into runnable curl commands, with header masking and KMP support.
News
Google summarizes top Android I/O productivity announcements, including Android CLI 1.0 stable and new Android skills.
Videos & Podcasts
Stevdza-San walks through a KMP starter template to jumpstart multiplatform projects without boilerplate setup.
alt
Android Developer Tips shares a 17-minute deep dive on how AI tools have changed the Android app-building workflow.
Android Developers summarizes the key Android developer productivity announcements from Google I/O.
Android Developers explains what Android skills are and how to expose them to AI assistants and tools.
Android Developers hosts a 66-minute Office Hours session covering Google Play policy review, enforcement, and publishing best practices.
Philipp Lackner covers the latest Android CLI update, exploring new commands that streamline device management and project setup.
Android Developers explains how to build adaptive Android UIs that respond to screen size, orientation, and input changes.
Firebase shows how to connect Gemini to Google Maps in an Android app using Firebase AI Logic.
Android Developers shows how to choose and tune prompts for faster AI inference on Android devices.
Android Developers introduces the new Styles API and explains its Compose performance benefits.
Android Developers covers the top three productivity announcements from Google I/O, including Android CLI stable release and new AI tools.
Kotlin by JetBrains interviews a Verifone architect on migrating legacy financial software to Kotlin safely.
Android Developers shows how a new AI-powered tool summarizes, organizes, and proposes improvements during code review.
Read the whole story
alvinashcraft
21 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Creating a multi-agent application – Part 4

1 Share

In part 3 we looked at creating the researcher. As promised, today we’ll look at the author.

You’ll notice in the following code a great deal of similarity to what we’ve seen before. The goal is to create a code “template” that we can follow as we create any agent; departing only for the agent’s special requirements and abilities.

As usual, we start with the factory method:

def create_author_chain():
    """Creates the author chain."""
    def author_invoke(state):
        research = state.get("research_findings", [])
        research_text = "\n\n".join(research) if research else "No research available."

        prompt = author_prompt_template.format(
            main_task=state.get("main_task", ""),
            research_findings=research_text,
            draft=state.get("draft", ""),
            review_notes=state.get("review_notes", "")
        )

        try:
            response = llm.invoke(prompt)
            content = response.content if hasattr(response, 'content') else str(response)
            return content if content else "Draft in progress..."
        except Exception as e:
            print(f"Author error: {e}")
            return "Error generating draft. Please try again."

    return author_invoke

# Creating a callable object
author_chain = create_author_chain()

We start by getting the research findings as a collection. We join all the entries into a single text string, or if there are no findings, we create the string “No research available.”

A local variable, prompt, is created from the author_prompt_template and passed to the invoke method of the LLM. The local variable content will contain the metadata or just the response from the LLM if there is no metadata.

The structure, so far, is identical to what we’ve seen before.

Next, we create the author_prompt_template used above,

author_prompt_template = """
You are a professional blogger.

Main Task: {main_task}

Research Findings:
{research_findings}

Current Draft: {draft}

Review Notes: {review_notes}

Instructions:
- If this is the first draft (no current draft), create a comprehensive post based on the findings
- If there is a current draft and review notes, revise the draft to address all feedback
- Use professional tone
- Make the post consise (aim for 250-500 words)

Write the complete post now:
"""

This is pretty self-explanatory. The interpolation variables (e.g., {draft}) will be filled in when the create_author_chain runs.

Finally, we create the author_node,

def author_node(state: ResearchState) -> dict:
    """Author node that creates or revises draft."""
    print("\n>>>Author")

    draft = author_chain(state)
    print(f"Draft created: {len(draft)} characters")

    return {
        "draft": draft,
        "revision_number": state.get("revision_number", 0) + 1
    }

Reviewer

The reviewer follows the same pattern, so we might as well look at it here. We start with the factory

def create_reviewer_chain():
    """Creates the reviewer chain."""
    def reviewer_invoke(state):
        draft = state.get("draft", "")
        revision_num = state.get("revision_number", 0)

        if len(draft.strip()) < 100:
            return "APPROVED - Draft is minimal but acceptable."

        if revision_num >= 4:
            return "APPROVED - Maximum revisions reached. The report is satisfactory."

        prompt = reviewer_prompt_template.format(
            main_task=state.get("main_task", ""),
            draft=draft
        )

        try:
            response = llm.invoke(prompt)
            content = response.content if hasattr(response, 'content') else str(response)
            return content if content else "APPROVED"
        except Exception as e:
            print(f"Review error: {e}")
            return "APPROVED - Error in review, proceeding with current draft."

    return reviewer_invoke

# Creating a callable object
reviewer_chain = create_reviewer_chain()

The reviewer will either approve the draft or kick it back directly to the author for revision. The only other part here is creating the node for the reviewer.

def reviewer_node(state: ResearchState) -> dict:
    """Node that reviews the draft."""
    print("\n>>REVIEWER")

    review = reviewer_chain(state)
    print(f"Review: {review[:100]}...")

    is_approved = "APPROVED" in review.upper()

    if is_approved:
        print("✓ Draft APPROVED")
        return {
            "review_notes": "APPROVED",
            "next_step": "END"
        }
    else:
        print("✗ Revisions needed")
        return {
            "review_notes": review,
            "next_step": "author"
        }

In the next post we’ll finally see how the nodes are used in creating a workflow.

Read the whole story
alvinashcraft
21 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Announcing Files v4.1.4

1 Share
Announcing Files Preview v4.1.4 for users of the preview version.

Read the whole story
alvinashcraft
21 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Your first Microsoft.UI.Reactor app

1 Share

Now that Microsoft.UI.Reactor and the project templates are on NuGet.org, getting started is a LOT simpler than it used to be.

If you want the absolute shortest path to a running app, it's really just this (assuming you already installed the .NET SDK):

dotnet new install Microsoft.UI.Reactor.ProjectTemplates
dotnet new reactorapp
dotnet run -a x64

That's it!

So let's take a quick look at what those three commands actually do, and more importantly what kind of app Reactor generates for you.

Creating the app

The first command installs the template:

dotnet new install Microsoft.UI.Reactor.ProjectTemplates

You only need to do this the first time.

Next:

dotnet new reactorapp

If you run that in an empty folder, the template uses the folder name as the project name and drops the generated files right there.

And finally:

dotnet run -a x64

That builds and launches the app with the x64 architecture, which is generally the right thing to do for a WinUI desktop app (if you are on ARM64 you can use arm64 instead).

If you prefer creating the app in a named folder instead, you can also do:

dotnet new reactorapp -n MyFirstReactorApp
cd MyFirstReactorApp
dotnet run -a x64

Project Contents

One of the nice things about the template is that it stays very small. You're not dropped into a huge starter app with a lot of moving parts. The interesting bits are basically just:

- App.cs
- <ProjectName>.csproj

And honestly, that's a pretty good first impression for Reactor, because the whole point is that your UI is just C# code.

Let's start with App.cs

The generated app looks like this:

using System;
using Microsoft.UI.Reactor;
using Microsoft.UI.Reactor.Core;
using Microsoft.UI.Reactor.Layout;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using static Microsoft.UI.Reactor.Factories;

ReactorApp.Run<App>("MyFirstReactorApp", width: 900, height: 600);

class App : Component
{
  public override Element Render()
  {
    var (name, setName) = UseState("World");

    var titleBar = TitleBar("MyFirstReactorApp").Flex(shrink: 0);

    var body = Border(
      FlexColumn(
        Heading($"Hello, {name}!"),
        TextBox(name, setName, placeholderText: "Your name")
           .AutomationName("NameInput")
      ) with { RowGap = 16 }
    ).Padding(24).Flex(grow: 1, basis: 0);

    return FlexColumn(titleBar, body)
        .Backdrop(BackdropKind.Mica);
    }
}


This is pretty simple, but at the same time there's actually a lot going on here, so let's break down the basics.
The very first interesting line is this one:

ReactorApp.Run<App>("MyFirstReactorApp", width: 900, height: 600);

This is the app bootstrap. Reactor opens the window, hosts the app, and renders your root component. If you're used to XAML app startup plumbing, this is refreshingly simple.

Then we get to the App component itself:

class App : Component
{
    public override Element Render()

The Render methods are the key to Reactor. You don't create a window and start mutating controls. Instead you render an element tree that describes what the UI should look like right now, and it returns a light-weight description of the UI - not the UI objects themselves.

The next line is probably the most important one in the whole sample, and if you're coming from XAML probably the most unfamiliar one:

var (name, setName) = UseState("World");

UseState is one of many "hooks" you'll find in Reactor. You have some state. You render UI from that state. Events update the state. Reactor re-renders and patches the native WinUI controls in place.

The sample keeps this super simple: the initial value is `"World"`, so the heading renders as:

Heading($"Hello, {name}!")

which means the app starts out showing `Hello, World!` using a Textblock with the Heading style. The "Heading" is merely a simple short-cut for creating a TextBlock with the style preset, to keep your code simple and concise. There are a bunch of these short-hand statements like HStack and VStack for Horizontal and Vertical StackPanels.

Then the text box wires straight into that same state:

TextBox(name, setName, placeholderText: "Your name")

This is a nice little detail because it immediately shows the controlled-input model. As you type into the text box, `setName` gets called, the component renders again, and the heading updates live.

So type `Reactor`, and the heading becomes `Hello, Reactor!`.

That's a tiny app, sure, but it's also the whole Reactor loop in one screen.

The rest of the file is mostly layout and presentation.

var titleBar = TitleBar("MyFirstReactorApp").Flex(shrink: 0);

gives you a title bar, while:

FlexColumn(...)
Border(...).Padding(24)

build up the body using ordinary C# composition instead of XAML markup.

And finally:

.Backdrop(BackdropKind.Mica);

adds a Mica backdrop so the sample already feels like a proper Windows app.

That last part is worth calling out: Reactor isn't drawing some fake custom UI surface. It's building real WinUI UI under the covers.

The project file is small too

The generated project file is also pretty lean:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net10.0-windows10.0.22621.0</TargetFramework>
    <Platforms>ARM64;X86;X64</Platforms>
    <UseWinUI>true</UseWinUI>
    <WindowsPackageType>None</WindowsPackageType>
    <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.WindowsAppSDK" Version="..." />
    <PackageReference Include="Microsoft.UI.Reactor" Version="..." />
  </ItemGroup>
</Project>

Again, nothing wild here, and that's a good thing.

UseWinUI makes this a WinUI project, WindowsPackageType is set to None so you start with an unpackaged desktop app, and Microsoft.UI.Reactor just comes in as a normal NuGet package.

That's really the big story here: Reactor now feels like a regular part of the .NET ecosystem. Install a template, create a project, run it. Edit it in Visual Studio, VS Code, or just Notepad for a bit of personal torture. Or have your favorite agent iterate on it. Because everything is code, you're more likely to get compile time errors instead of XAML runtime errors which really helps with both your and an agent's inner dev-loop.

What you get when it runs

When the app starts up, you get a small native desktop window with a title bar, a greeting, and a text box asking for your name.

Type in the box, and the heading updates immediately.

That might not s- ound like much, but it's actually a very good first sample because it teaches the right thing right away: the UI is a function of state.

No XAML. No view models. No binding setup. Just state in, UI out.

A lot of starter templates try too hard to impress you. They throw in navigation, settings pages, MVVM layers, mock data, half a dozen folders, and a bunch of code you're supposed to delete later.

This one doesn't.

It gives you one component, one piece of state, one layout, and one interaction. Just enough to understand the model, and not so much that you have to reverse engineer the template before you can start building your own app.

And that's probably exactly what a first Reactor app should be.

Where to next?

If you want learn more start with the Build 2026 presentation which covers a lot of the basics:

- Building WinUI Apps with C# First Patterns and AI Assisted Workflows

Next check out the official Reactor Documentation, but I'd like to call out a few key pages to study:

Next go study the samples. Here are a few I found interesting:

  • Reactor Gallery - Lots of great samples in one big demo app. Run this app and study each sample.
  • Validation Showcase - Great example of how to do even intricate input validation with very little code.
  • Reactor IDE - A Visual-Studio style app demonstrating docking/draggable windows.
  • Particle Storm - High-performance particle rendering using Win2D.
Read the whole story
alvinashcraft
22 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Parsing JSON at compile time with C++26 static reflection

1 Share

Suppose that you have a configuration file in JSON. Something like this:

{ "width": 1920, "height": 1080, "fullscreen": true,
  "title": "My Game", "volume": 0.8 }

Normally you ship this file alongside your program, open it at startup, read it, and parse it. That is a lot of work for data that never changes. What if the file is fixed at build time? Could the compiler read it, parse it, and bake the result directly into the executable as a constant?

With C++26, the answer is yes. We need two new ingredients, all of which are usable right now with the latest version of the GCC compiler (16).

  1. #embed to pull the file into the program at compile time,
  2. A software library supporting static reflection like simdjson.

Let me show you how far we can take this.

The new #embed directive reads a file and expands it into a comma-separated list of byte values. To read the file data.json at compile time and keep it around as a constant, we write:

constexpr const char json_data[] = {
#embed "data.json"
    , 0
};

I use constexpr because I want the compiler to be allowed to inspect these bytes during constant evaluation. The trailing , 0 simply appends a null terminator, so the array can be treated as an ordinary C string.

There is no run-time input/output of any kind. The bytes are part of the program.

But embedded bytes are not yet useful by themselves. What I really want is a typed C++ object. In my example, the target type is this configuration struct:

struct Window {
  int         width;
  int         height;
  bool        fullscreen;
  std::string title;
  double      volume;
};

The traditional way to populate such a struct from JSON is to write, by hand, one line per field: read "width", store it into width, read "height", store it into height, and so on. It is tedious. And because it runs at startup, a malformed file becomes a run-time error, discovered by your users rather than by you.

Recent versions of simdjson can parse JSON at compile time using C++26 static reflection. The entry point is simdjson::compile_time::parse_json, and it does something I still find slightly magical: it reads the JSON and, from the keys it finds, and synthesises the struct type for you.

#define SIMDJSON_STATIC_REFLECTION 1
#include "simdjson.h"
constexpr const char json_data[] = {
#embed "data.json"
    , 0
};
constexpr auto window = simdjson::compile_time::parse_json<json_data>();

The variable window is a value computed entirely by the compiler. Its type is generated from the document: it has a width and a height (both 64-bit integers), a bool fullscreen, a double volume, and a title. From here on I write window.width and it behaves like any ordinary field.

How do I know the parsing really happened at compile time? Because I can assert things about the result that the compiler must check before the program even exists:

static_assert(window.width      == 1920);
static_assert(window.height     == 1080);
static_assert(window.fullscreen == true);

If I corrupt the JSON — delete a brace, misspell true, leave a trailing comma — the program no longer compiles, and the error points at the parse_json line. The broken file is caught at build time, on my machine, instead of at startup on someone else’s.

Because window is a genuine compile-time constant, any computation over it is a constant too. Consider this function:

int  screen_area()   { return window.width * window.height; }

Compiled with -O3, there is no multiplication, no field access, and certainly no parsing left — only the answers, as immediate values (here on my macBook):

screen_area:    mov  w0, #0xa400        // 0x1fa400 = 2073600
                movk w0, #0x1f, lsl #16
                ret

The JSON has vanished from the binary. It was read and parsed exactly once, by the compiler, and all that survives is the number 2073600.

Because static reflection is so new, when building with GCC 16, you need to pass the flags -std=c++26 -freflection: the -freflection flag is necessary to activate compile-time reflection You must also set the simdjson macro SIMDJSON_STATIC_REFLECTION=1 before importing the simdjson.h. It is a temporary safeguard.

The source code to reproduce these examples is available.

Reference: P2996 — Reflection for C++26 and the simdjson library.

Credit: The simdjson implementation is joint work with Francisco Geiman Thiesen.

Read the whole story
alvinashcraft
22 minutes ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories