#0435 – Why Markov chains and GPTs feel similar — and why they’re not


I was watching a podcast the other day, and the topic of Markov chains came up in the context of deterministic v/s indeterministic behavior. By the end of that podcast, the modern GPTs started to come to mind. Modern AI models like GPTs often get described as “predicting the next word,” which sounds a lot like what Markov chains do. But the similarity ends at the surface. Underneath, the two systems behave in fundamentally different ways – especially when it comes to predictability and determinism.

This post outlines the journey I took to understand why they are different, even though they feel familiar.

Both systems predict the next token, but they do it with fundamentally different architectures and guarantees. Markov chains use explicit, discrete transitions; GPTs compute a continuous, context‑dependent distribution using attention and deep layers.

How Markov Chains Think: Fixed Paths, Fixed Futures

A Markov chain is built on a simple idea: the next state depends only on the current state.

This creates a world where:

  • The system has discrete states (like words or events).
  • Each state has a fixed set of outgoing transitions.
  • The probabilities of those transitions are explicitly stored.
  • The structure of the system is fully deterministic, even if the outcome is probabilistic.
[ Current State: token t ]


[ Transition Table: P(next | token t) ]


[ Sample next token t+1 ]

Think of it as a railway network:

  • Every station has a fixed set of tracks.
  • You can roll dice to choose which track to take.
  • But the tracks themselves never change.

Even when randomness is involved, the space of possible outcomes is fixed and predictable.

How GPTs Think: Continuous Context, Infinite Possibilities

GPTs also predict the next token—but the mechanism is radically different.

Instead of fixed states and fixed transitions, GPTs operate in a continuous, high‑dimensional space. The “state” of the model at any moment is not a token—it’s a context vector built from:

  • all previous tokens
  • their relationships
  • their semantic meaning
  • the model’s learned internal representations

This means:

  • There are no fixed outgoing edges.
  • The model recomputes the next-token distribution from scratch each time.
  • The same input token can lead to entirely different outputs depending on context.
  • The system’s behavior is non-deterministic by design.
[ Input tokens: t1, t2, ..., tn ]


[ Embedding layer → token vectors ]


[ Transformer blocks (self‑attention + FFN) ]
• each block: attend to all previous tokens
• build contextual hidden state h


[ Output projection → logits over vocab ]


[ Softmax → P(next token | full context) ]


[ Sample or pick next token ]

It’s less like a railway network and more like a GPS:

  • It recalculates the route every time.
  • The route depends on the entire environment.
  • Two identical starting points can lead to different paths depending on context.

A side-by-side comparison

FeatureMarkov ChainsGPT/Transformer
StateDiscrete tokenContinuous context vector
MemoryLocal (last state or few tokens)Global within context window
TransitionsStored, fixed probabilitiesComputed dynamically via attention
PredictabilityStructure predictable; outcomes probabilistic but boundedOutputs can vary widely for same token depending on context
ExpressivenessLimited to local patternsCaptures long‑range dependencies and abstractions
Typical useSimple text generators, stochastic processesLarge‑scale language generation, reasoning, code, dialogue

Predictability, determinism, and creativity — a nuanced view

  • Markov chains trend toward predictability. Given the transition graph and enough time, behavior converges to stationary patterns; the space of possible futures is fixed and enumerable.
  • GPTs produce controlled non‑determinism. The same prompt can yield different outputs because the model samples from a context‑shaped distribution; the model’s internal representation encodes nuanced semantic and syntactic signals rather than fixed outgoing edges. Experimental comparisons show Markov/n‑gram generators quickly collapse into local loops, while transformer models sustain global coherence and richer continuations.

Important distinction: non‑determinism in GPTs is not random noise — it’s structured variability. Sampling temperature, top‑k/top‑p, and the model’s learned logits shape how creative or conservative the output is.

Markov:   [state] --(stored probs)--> sample --> next state
Transformer:
[context] --(compute logits via attention & FFN)--> softmax --> sample --> next token
  • In Markov models, randomness is applied to a fixed distribution.
  • In transformers, randomness is applied to a computed distribution that depends on the entire context.

In conclusion

Markov chains store transitions. GPTs compute them.

That single architectural shift—from discrete stored edges to continuous, context‑driven computation—turns a predictable stochastic system into a generator capable of controlled creativity and long‑range coherence.

Until we meet next time,

Be courteous. Drive responsibly.

#0434 – SQL Server – Stored Procedures – Results are not returned if the caller does not specify the OUTPUT clause


A few weeks ago, I wrote a piece on returning scalar values from a stored procedure using the OUTPUT clause. Shortly after, I got an interesting comment stating that the example outlined in the post was not working. There were no errors – it’s just that the output variables weren’t being set when the stored procedure was called.

Here’s the stored procedure which was shared in my earlier post.

USE [tempdb];
GO
IF OBJECT_ID('dbo.proc_AddNumbersWithMultipleOutputs','P') IS NOT NULL
BEGIN
    DROP PROCEDURE [dbo].[proc_AddNumbersWithMultipleOutputs];
END
GO
 
CREATE PROCEDURE [dbo].[proc_AddNumbersWithMultipleOutputs]
    @intA INT,
    @intB INT,
    @isSuccessfullyProcessed BIT OUTPUT, --Output variable #1
    @resultC                 INT OUTPUT  --Output variable #2
AS
BEGIN
    SET NOCOUNT ON;
 
    SET @isSuccessfullyProcessed = 0;
    SET @resultC                 = 0;
 
    BEGIN TRY
        IF ((@intA IS NULL) OR (@intB IS NULL))
            THROW 51000,'Input Parameters cannot be NULL',-1;
 
        SET @resultC = @intA + @intB;
        SET @isSuccessfullyProcessed = 1;
    END TRY
    BEGIN CATCH
        SET @isSuccessfullyProcessed = 0;
        SELECT ERROR_NUMBER(),
               ERROR_MESSAGE(),
               ERROR_SEVERITY(),
               ERROR_STATE();
    END CATCH
END
GO

And here’s the script that was not working as expected:

--In a separate window, run the following 
DECLARE @inputA  INT = 2; --Use NULL to throw exception
DECLARE @inputB  INT = 3; --Use NULL to throw exception
DECLARE @outputC INT;
DECLARE @processingState INT;
 
EXEC [dbo].[proc_AddNumbersWithMultipleOutputs] 
    @intA                    = @inputA,
    @intB                    = @inputB,
    @isSuccessfullyProcessed = @processingState,
    @resultC                 = @outputC;
 
SELECT @inputA          AS 'Input A', 
       @inputB          AS 'Input B',
       @outputC         AS 'Result', 
       @processingState AS 'IsTransactionSuccessfullyProcessed';
GO
Screenshot showing no values were returned because OUTPUT parameter option was not specified.

If you carefully review the script, you will realize that @processingState and @outputC do not have the OUTPUT parameter specification. Without this specification, SQL Server treats it as a normal input parameter. Hence, no value was being returned. No errors should be expected as well. This behavior is the default and backward compatible.

Further reading

Until we meet next time,

Be courteous. Drive responsibly.

#0433 – SQL Server – Tips – Returning parameters from stored procedures (Part 2 of 2)


Last week, I wrote about how to share tables with stored procedures. This allows data sets to be passed in and then returned with modifications. In case you missed it, do read my post here: https://nakulvachhrajani.com/2025/08/04/0432-sql-server-tips-returning-parameters-from-stored-procedures-part-1-of-2/.

Returning result sets and sharing tables is great when there is little post processing involved. Applications that need to process individual entities, do so after the procedure returns them. Returning a cursor as an output parameter can be more efficient compared to returning a large result set. A forward-only cursor allows the application to iterate through the cursors and retrieve data as required v/s keeping the entire result set in memory.

An Example

Here’s an example on how to return cursors from a stored procedure. What I have here are two (2) stored procedures.

Procedure: The “inner” procedure – returning the cursor

USE [WideWorldImporters];
GO

IF OBJECT_ID('dbo.usrproc_GetSupplierList', 'P') IS NOT NULL
    DROP PROCEDURE dbo.usrproc_GetSupplierList;
GO

CREATE PROCEDURE dbo.usrproc_GetSupplierList
	@supplierListCursor CURSOR VARYING OUTPUT
AS
BEGIN
	SET NOCOUNT ON;

	SET @supplierListCursor = CURSOR FORWARD_ONLY STATIC
		FOR SELECT [ws].[SupplierID],
				   [ws].[SupplierName],
				   [ws].[SupplierReference]
			FROM [Website].[Suppliers] AS [ws] WITH (NOLOCK);
	OPEN @supplierListCursor;
END
GO

Consumer: The “outer” script – consuming the cursor

USE [WideWorldImporters];
GO

DECLARE @sqlTwinsCursor     AS CURSOR;
DECLARE @supplierIdentifier AS INT;
DECLARE @supplierName       AS NVARCHAR(100);
DECLARE @supplierReference  AS NVARCHAR(20);

--OUTPUT clause brings the CURSOR 
EXECUTE dbo.usrproc_GetSupplierList
    @supplierListCursor = @sqlTwinsCursor OUTPUT;

WHILE (@@FETCH_STATUS = 0)
    BEGIN
        FETCH NEXT FROM @sqlTwinsCursor INTO @supplierIdentifier, 
											 @supplierName, 
											 @supplierReference;

		--Consume the values 
		SELECT [ppo].[PurchaseOrderID],
		       [ppo].[SupplierID],
			   [ppo].[OrderDate],
			   [ppo].[ExpectedDeliveryDate],
			   [ppo].[DeliveryMethodID],
			   [ppo].[IsOrderFinalized]
		FROM [Purchasing].[PurchaseOrders] AS [ppo]
		WHERE [ppo].[SupplierID] = @supplierIdentifier
		  AND [ppo].[SupplierReference] = ISNULL (@supplierReference,[ppo].[SupplierReference]);
    END

--Close & deallocate the cursor
CLOSE @sqlTwinsCursor;

DEALLOCATE @sqlTwinsCursor;
GO

In case you are following along, here’s how the output should look like:

Cursor States

Cursor states only matter at return time. As outlined in the official documentation:

“It’s valid to close a cursor part of the way through the procedure, to open it again later in the procedure, and return that cursor’s result set to the calling batch, procedure, or trigger.”

I trust you will find the ability to return cursors from stored procedures helpful in improving the efficiency of your applications. Drop a note in the comments below on how you used it and the benefits you got.

Further Reading

Until we meet next time,

Be courteous. Drive responsibly.

#0432 – SQL Server – Tips – Returning parameters from stored procedures (Part 1 of 2)


In my earlier post, I talked about returning scalar values from a stored procedure using the OUTPUT clause. And that made to share a few tips that I have learnt and used through the years.

Returning scalar values is a relatively simple ask. But what if we need to return the entire result set?

The answer is simple – it can’t be done with an OUTPUT clause. Returning a table has to be done in the classical way (been in use since SQL 2000)!

So, today, I will show how to return a table from a stored procedure. Let’s run a quick test.

The Test

The test scenario here is straight-forward. I have a stored procedure ([dbo].[usrproc_SharingTempTablesTesting]) that accepts some inputs and stores the values into a temp. table. The contents of that table are retrieved outside the procedure – thus proving that the table was shared.

The stored procedure is below:

USE [tempdb];
GO

--00. Safety check
IF OBJECT_ID('usrproc_SharingTempTablesTesting') IS NOT NULL
BEGIN
    DROP PROCEDURE usrproc_SharingTempTablesTesting;
END
GO

--01. Create stored procedure
CREATE PROCEDURE [dbo].[usrproc_SharingTempTablesTesting]
    @intA INT,
    @intB INT
AS
BEGIN
  SET NOCOUNT ON

  INSERT INTO #SharingTempTables ([PlaceInserted], [iA], [iB]) 
  VALUES ('StoredProcedure', @intA, @intB)
 
  --Selecting data from the temp. table
  SELECT 'Inside',* FROM #SharingTempTables  
END
GO

Now, let’s create a temporary table, populate it with values and then call the stored procedure and consume the output:

USE [tempdb];
GO
--00. Safety Check
IF OBJECT_ID('#SharingTempTables') IS NOT NULL
BEGIN
    DROP TABLE #SharingTempTables;
END
GO

--01. Create temp. table
CREATE TABLE #SharingTempTables ([PlaceInserted] NVARCHAR(255) NOT NULL,
                                 [iA]            INT           DEFAULT 0, 
                                 [iB]            INT           DEFAULT 0,
                                 [iT]            AS (iA + iB)
                                )
GO

--02. Insert some test data
INSERT INTO #SharingTempTables ([PlaceInserted], [iA], [iB])
VALUES ('Outside SP', 2, 5), 
       ('Outside SP', 3, 9);
GO

--03. Execute the stored procedure
EXEC [dbo].[usrproc_SharingTempTablesTesting] @intA = 4, @intB = 2;
GO

--04. Select data from the temp table
SELECT 'Outside',* FROM #SharingTempTables
GO

I trust this simple demonstration will help you to understand how to exchange information into a stored procedure and refer the same outside too.

Until we meet next time,

Be courteous. Drive responsibly.

#0431 – SQL Server – Myths – Can a stored procedure have more than one OUTPUT variables?


It’s a fairly common practice to use the OUTPUT clause to return scalar output from a stored procedure. Today’s post is based on a question that I was asked by one of my colleagues related to the usage of OUTPUT clause:

“Can a stored procedure have more than one OUTPUT variables?”

The short answer is Yes! Not only that – it also possible to have non-scalar outputs from stored procedures. But more about that in another post.

Here’s a quick example of a stored procedure having more than one (1) OUTPUT variables. It’s a simple procedure that validates if the input parameters are NULL and subsequently adds two (2) integers. At the end, stored procedure returns 2 variables – the processing state and the result of the addition.

USE [tempdb];
GO
IF OBJECT_ID('dbo.proc_AddNumbersWithMultipleOutputs','P') IS NOT NULL
BEGIN
	DROP PROCEDURE [dbo].[proc_AddNumbersWithMultipleOutputs];
END
GO

CREATE PROCEDURE [dbo].[proc_AddNumbersWithMultipleOutputs]
	@intA INT,
	@intB INT,
	@isSuccessfullyProcessed BIT OUTPUT, --Output variable #1
	@resultC                 INT OUTPUT  --Output variable #2
AS 
BEGIN
	SET NOCOUNT ON;

	SET @isSuccessfullyProcessed = 0;
	SET @resultC                 = 0;

	BEGIN TRY
		IF ((@intA IS NULL) OR (@intB IS NULL))
			THROW 51000,'Input Parameters cannot be NULL',-1;

		SET @resultC = @intA + @intB;
		SET @isSuccessfullyProcessed = 1;
	END TRY
	BEGIN CATCH
		SET @isSuccessfullyProcessed = 0;
		SELECT ERROR_NUMBER(),
		       ERROR_MESSAGE(),
			   ERROR_SEVERITY(),
			   ERROR_STATE();
	END CATCH
END
GO

And here’s the procedure in action. The first query indicates successful execution.

--In a separate window, run the following 
DECLARE @inputA  INT = 2; --Use NULL to throw exception
DECLARE @inputB  INT = 3; --Use NULL to throw exception
DECLARE @outputC INT;
DECLARE @processingState INT;

EXEC [dbo].[proc_AddNumbersWithMultipleOutputs] 
	@intA                    = @inputA,
    @intB                    = @inputB,
	@isSuccessfullyProcessed = @processingState OUTPUT,
	@resultC                 = @outputC OUTPUT;

SELECT @inputA          AS 'Input A', 
       @inputB          AS 'Input B',
	   @outputC         AS 'Result', 
	   @processingState AS 'IsTransactionSuccessfullyProcessed';
GO

The following query with NULL input parameters will result in an exception.

--In a separate window, run the following 
DECLARE @inputA  INT = 2; --Use NULL to throw exception
DECLARE @inputB  INT = NULL;
DECLARE @outputC INT;
DECLARE @processingState INT;

EXEC [dbo].[proc_AddNumbersWithMultipleOutputs] 
	@intA                    = @inputA,
    @intB                    = @inputB,
	@isSuccessfullyProcessed = @processingState OUTPUT,
	@resultC                 = @outputC OUTPUT;

SELECT @inputA          AS 'Input A', 
       @inputB          AS 'Input B',
	   @outputC         AS 'Result', 
	   @processingState AS 'IsTransactionSuccessfullyProcessed';
GO

I trust this simple demonstration will help you to write more effective T-SQL code.

Until we meet next time,

Be courteous. Drive responsibly.